2013-05-30 31 views
13

TL; DRtworzenie ELF delokalizacji - Zrozumienie relocs, symbole danych sekcji i jak one działają razem

Próbowałem zrobić to krótkie pytanie, ale jest to skomplikowany problem, więc skończyło się to długo. Jeśli możesz odpowiedzieć na jakąkolwiek część tego lub podać sugestie lub wskazówki lub zasoby lub cokolwiek w ogóle, byłoby to bardzo pomocne (nawet jeśli nie rozwiązujesz bezpośrednio wszystkich moich problemów). W tej chwili walę głową w ścianę. :)

Oto konkretne problemy, które mam. Czytaj dalej poniżej, aby uzyskać więcej informacji.

  • Szukam wskazówek dotyczących przetwarzania wpisów dotyczących relokacji i aktualizowania nierozwiązanych symboli w danych sekcji. Po prostu nie rozumiem, co zrobić z wszystkimi informacjami, które wyciągnąłem z przeniesień i sekcji, itp.
  • Mam również nadzieję, że zrozumiem, co się dzieje, gdy linker napotyka na przesiedlenia. Próba poprawnego wdrożenia równań relokacji i poprawnego wykorzystania wszystkich poprawnych wartości jest niesamowicie trudna.
  • Kiedy napotykam na kody operacyjne, adresy i symbole itp., Muszę wiedzieć, co z nimi zrobić. Czuję, że brakuje mi kilku kroków.
  • Czuję, że nie mam dobrego zrozumienia, w jaki sposób wpisy w tablicy symboli wchodzą w interakcje z relokacjami. Jak używać informacji o powiązaniu, widoczności, wartości i wielkości symbolu?
  • Wreszcie, kiedy wysyłam plik z danymi rozwiązanymi i nowymi pozycjami relokacji używanymi przez plik wykonywalny, dane są niepoprawne. Nie jestem pewien, jak śledzić wszystkie delokalizacje i dostarczyć wszelkich niezbędnych informacji. Co to jest plik wykonywalny, który oczekuje ode mnie?

Moje podejście do tej pory

Próbuję utworzyć plik delokalizacji w zadaniu [Nieudokumentowane] własnym formacie, który jest w dużej mierze oparty na ELF. Napisałem narzędzie, które pobiera plik ELF i częściowo połączony plik (PLF) i przetwarza je w celu wyprowadzenia w pełni rozwiązanego pliku rel. Ten plik rel służy do ładowania/rozładowywania danych w razie potrzeby w celu zaoszczędzenia pamięci. Platforma to 32-bitowy PPC. Jedna zmarszczka to to, że to narzędzie jest napisane dla Windows w języku C#, ale dane są przeznaczone dla PPC, więc są zabawne problemy z endianami i tym podobne, na które trzeba uważać.

Próbowałem zrozumieć, w jaki sposób przenoszone są relokacje, gdy są używane do rozwiązywania nierozwiązanych symboli i tak dalej. Co dotychczas zrobiłem, to skopiować odpowiednie sekcje z PLF, a następnie dla każdej odpowiadającej sekcji .rela, analizuję wpisy i próbuję naprawić dane sekcji i generować nowe wpisy relokacji w razie potrzeby. Ale tu jest moja trudność. Wychodzę z mojego żywiołu i takie rzeczy zdają się zwykle robić przez linkery i ładowarki, więc nie ma wielu dobrych przykładów. Ale znalazłem kilka, które pomogły, w tym THIS ONE.

Więc co się dzieje jest:

  1. dane sekcja skopiować z PLF być wykorzystywane do pliku rel. Interesują mnie tylko .init (brak danych), .text, .ektory, .dtors, .rodata, .data, .bss (brak danych) i inna niestandardowa sekcja, której używamy.
  2. Powtórz te fragmenty w PLF i przeczytaj w pozycjach Elf32_Rela.
  3. Dla każdego wpisu wyciągam pola r_offset, r_info i r_addend i wyodrębniam odpowiednie informacje z r_info (symbol i typ relokacji).
  4. Z tabeli symboli PLF można uzyskać symbolOffset, symbolSection i symbolValue.
  5. Z ELF otrzymuję adres obciążenia symbolSection.
  6. I compute int localAddress = (.relaSection.Offset + r_offset).
  7. Otrzymuję wartość relacyjną uint z treści symboluSection w r_offset.
  8. Teraz mam wszystkie potrzebne informacje, więc włączam typ reloksu i przetwarzam dane. Są to typy Popieram:
    R_PPC_NONE
    R_PPC_ADDR32
    R_PPC_ADDR24
    R_PPC_ADDR16
    R_PPC_ADDR16_LO
    R_PPC_ADDR16_HI
    R_PPC_ADDR16_HA
    R_PPC_ADDR14
    R_PPC_ADDR14_BRTAKEN
    R_PPC_ADDR14_BRNTAKEN
    R_PPC_REL24
    R_PPC_REL14
    R_PPC_REL14_BRTAKEN
    R_PPC_REL14_BRNTAKEN
  9. Co teraz? Muszę zaktualizować dane sekcji i utworzyć wpisy relokacji towarzyszącej. Ale nie rozumiem, co trzeba zrobić i jak to zrobić.

Cały powód, dla którego to robię, polega na tym, że istnieje stare, przestarzałe, nieobsługiwane narzędzie, które nie obsługuje niestandardowych sekcji, co jest kluczowym wymogiem dla tego projektu (ze względu na pamięć). Mamy sekcję niestandardową zawierającą pakiet kodu inicjalizacyjnego (o wartości około megapiksela), który chcemy rozładować po uruchomieniu. Istniejące narzędzie po prostu ignoruje wszystkie dane w tej sekcji.

Podczas gdy tworzenie własnego narzędzia, które obsługuje niestandardowe sekcje, jest idealne, jeśli istnieją jakieś jasne pomysły na inny sposób osiągnięcia tego celu, jestem uszy! Pomyśleliśmy o tym, jak korzystać z sekcji .dtor dla naszych danych, ponieważ i tak jest prawie pusta. Ale jest to niechlujne i może nie działać tak czy inaczej, jeśli uniemożliwi czyste wyłączenie.


delokalizacji oraz przykładowy kod

Kiedy przetwarzać delokalizacji, pracuję od równań i informacji znalezionych w docs ABI HERE (ok sekcji 4.13, strona 80ish), jak również wiele innych przykładów kodu i blogów, które wykopałem. Ale wszystko to jest tak mylące i niezbyt wyraźne, a cały kod, który znalazłem, robi to trochę inaczej.

Przykładowo

  • R_PPC_ADDR16_LO -> half16: #lo (S + A)
  • R_PPC_ADDR14_BRTAKEN -> low14 * (S + A) >> 2
  • itp

Kiedy więc zobaczę ten rodzaj kodu, jak mogę go odszyfrować?

Oto jeden przykład (od this source)

case ELF::R_PPC64_ADDR14 : { 
    assert(((Value + Addend) & 3) == 0); 
    // Preserve the AA/LK bits in the branch instruction 
    uint8_t aalk = *(LocalAddress+3); 
    writeInt16BE(LocalAddress + 2, (aalk & 3) | ((Value + Addend) & 0xfffc)); 
} break; 

case ELF::R_PPC64_REL24 : { 
    uint64_t FinalAddress = (Section.LoadAddress + Offset); 
    int32_t delta = static_cast<int32_t>(Value - FinalAddress + Addend); 
    if (SignExtend32<24>(delta) != delta) 
     llvm_unreachable("Relocation R_PPC64_REL24 overflow"); 
    // Generates a 'bl <address>' instruction 
    writeInt32BE(LocalAddress, 0x48000001 | (delta & 0x03FFFFFC)); 
} break; 

Oto niektóre z innym przykładzie (here)

case R_PPC_ADDR32: /* word32 S + A */ 
    addr = elf_lookup(lf, symidx, 1); 
    if (addr == 0) 
     return -1; 
    addr += addend; 
    *where = addr; 
    break; 

case R_PPC_ADDR16_LO: /* #lo(S) */ 
    if (addend != 0) { 
     addr = relocbase + addend; 
    } else { 
     addr = elf_lookup(lf, symidx, 1); 
     if (addr == 0) 
      return -1; 
    } 
    *hwhere = addr & 0xffff; 
    break; 

case R_PPC_ADDR16_HA: /* #ha(S) */ 
    if (addend != 0) { 
     addr = relocbase + addend; 
    } else { 
     addr = elf_lookup(lf, symidx, 1); 
     if (addr == 0) 
      return -1; 
    } 
    *hwhere = ((addr >> 16) + ((addr & 0x8000) ? 1 : 0)) & 0xffff; 
    break; 

I jeszcze jeden przykład (from here)

case R_PPC_ADDR16_HA: 
    write_be16 (dso, rela->r_offset, (value + 0x8000) >> 16); 
    break; 
case R_PPC_ADDR24: 
    write_be32 (dso, rela->r_offset, (value & 0x03fffffc) | (read_ube32 (dso, rela->r_offset) & 0xfc000003)); 
    break; 
case R_PPC_ADDR14: 
    write_be32 (dso, rela->r_offset, (value & 0xfffc) | (read_ube32 (dso, rela->r_offset) & 0xffff0003)); 
    break; 
case R_PPC_ADDR14_BRTAKEN: 
case R_PPC_ADDR14_BRNTAKEN: 
    write_be32 (dso, rela->r_offset, (value & 0xfffc) 
            | (read_ube32 (dso, rela->r_offset) & 0xffdf0003) 
            | ((((GELF_R_TYPE (rela->r_info) == R_PPC_ADDR14_BRTAKEN) << 21) 
            ^(value >> 10)) & 0x00200000)); 
    break; 
case R_PPC_REL24: 
    write_be32 (dso, rela->r_offset, ((value - rela->r_offset) & 0x03fffffc) | (read_ube32 (dso, rela->r_offset) & 0xfc000003)); 
    break; 
case R_PPC_REL32: 
    write_be32 (dso, rela->r_offset, value - rela->r_offset); 
    break; 

naprawdę chcę zrozumieć magię, którą ci faceci robią g i dlaczego ich kod nie zawsze wygląda tak samo. Sądzę, że część kodu przyjmuje założenia, że ​​dane zostały już odpowiednio zamaskowane (dla gałęzi, itp.), A część kodu nie. Ale w ogóle tego nie rozumiem.


następstwie symbole/data/delokalizacji itp

Kiedy patrzę na dane w edytor heksadecymalny, widzę kilka "48 00 00 01" na całym ciele. Zorientowałem się, że jest to kod operacyjny i trzeba go zaktualizować o informacje o relokacji (w szczególności dotyczy to gałęzi i łącza "bl"), ale moje narzędzie nie działa na większości z nich i tych, które robię update ma w nich błędne wartości (w porównaniu do przykładu wykonanego przez przestarzałe narzędzie). Najwyraźniej brakuje mi części procesu.

Oprócz danych sekcji, istnieją dodatkowe wpisy relokacji, które należy dodać na końcu pliku rel. Składają się na to relokacje wewnętrzne i zewnętrzne, ale jeszcze się o nich nie dowiedziałem. (Jaka jest różnica między tymi dwoma i kiedy używasz jednego lub drugiego?)

Jeśli spojrzysz pod koniec this file w funkcji RuntimeDyldELF::processRelocationRef, zobaczysz kilka wpisów dotyczących relokacji. Robią również funkcje pośredniczące. Podejrzewam, że to dla mnie brakujące ogniwo, ale jest tak przejrzyste jak błoto i nie podążam za nim nawet trochę.

Kiedy wyprowadzam symbole w każdym wpisie relokacji, każdy z nich ma powiązanie/widoczność [Globalny/Słabe/Lokalne] [Funkcja/Obiekt] oraz wartość, rozmiar i przekrój. Wiem, że sekcja jest tam, gdzie znajduje się symbol, a wartość jest przesunięciem do symbolu w tej sekcji (czy jest to adres wirtualny?). Rozmiar jest wielkością symbolu, ale czy jest to ważne? Może globalne/słabe/lokalne jest przydatne do określenia, czy jest to relokacja wewnętrzna czy zewnętrzna?

Może ta tablica relokacji, o której mówię, jest faktycznie tablicą symboli dla mojego pliku rel? Może ta tabela aktualizuje wartość symbolu z bycia wirtualnym adresem do bycia przesunięciem sekcji (ponieważ to jest wartość w relokowalnych plikach, a tabela symboli w PLF jest w zasadzie w pliku wykonywalnym)?


Niektóre zasoby:

  1. blogi na delokalizacji: http://eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/
  2. wspomina opcodes na koniec: http://wiki.netbsd.org/examples/elf_executables_for_powerpc/
  3. mój pokrewne pytanie bez odpowiedzi: ELF Relocation reverse engineering

Whew! To jest bestia z pytaniem. Gratulacje, jeśli dotarłeś tak daleko. :) Z góry dziękuję za wszelką pomoc, jaką możesz mi dać.

+0

możliwy duplikat [Koncepcja przeniesienia] (http://stackoverflow.com/questions/16385826/concept-of-relocation) Szczegółowy minimalny przykład na stronie: http://stackoverflow.com/a/30507725/895245 –

Odpowiedz

14

Natknęłam się na to pytanie i pomyślałam, że zasługuje na odpowiedź.

Mają elf.h przydatne. Możesz go znaleźć w Internecie.

Każda sekcja RELA zawiera tablicę wpisów Elf32_Rela, jak wiesz, ale jest również powiązana z pewną inną sekcją. r_offset jest przesunięciem do innej sekcji (w tym przypadku - it works differently for shared libraries). Przekonasz się, że nagłówki sekcji mają członka o nazwie sh_info. To powie Ci, która sekcja jest. (Jest to indeks do tabeli nagłówków sekcji, jak można się spodziewać.)

"Symbol", który otrzymałeś od r_info, jest w rzeczywistości indeksem do tablicy symboli znajdującej się w innej sekcji. Szukaj członka sh_link w nagłówku sekcji RELA.

Tabela symboli podaje nazwę szukanego symbolu w postaci elementu st_name Elf32_Sym. st_name jest przesunięciem w sekcji łańcuchowej. Która sekcja pochodzi od elementu sh_link nagłówka sekcji tabeli symboli. Przepraszam, jeśli to zagmatwa.

Elf32_Shdr *sh_table = elf_image + ((Elf32_Ehdr *)elf_image)->e_shoff; 
Elf32_Rela *relocs = elf_image + sh_table[relocation_section_index]->sh_offset; 

unsigned section_to_modify_index = sh_table[relocation_section_index].sh_info; 
char *to_modify = elf_image + sh_table[section_to_modify_index].sh_offset; 

unsigned symbol_table_index = sh_table[relocation_section_index].sh_link; 
Elf32_Sym *symbol_table = elf_image + sh_table[symbol_table_index].sh_offset; 

unsigned string_table_index = sh_table[symbol_table].sh_link; 
char *string_table = elf_image + sh_table[string_table_index].sh_offset; 

Załóżmy, że pracujemy z numerem przeniesienia i.

Elf32_Rela *rel = &relocs[i]; 
Elf32_Sym *sym = &symbol_table[ELF32_R_SYM(rel->r_info)]; 
char *symbol_name = string_table + sym->st_name; 

Znajdź adres tego symbolu (powiedzmy, że symbol_name == "printf"). Zostanie wprowadzona ostateczna wartość (to_modify + rel-> r_offset).

Jeśli chodzi o tabelę na stronach 79-83 pliku pdf, który łączyłeś, informuje nas, co umieścić pod tym adresem i ile bajtów należy zapisać. Oczywiście adres, który właśnie dostaliśmy (w tym przypadku printf) jest częścią większości z nich. Odpowiada ono S w wyrażeniach.

r_addend to po prostu A. Czasami kompilator musi dodać stałą statyczną do reloksu.

B to adres bazowy obiektu współdzielonego lub 0 dla programów wykonywalnych, ponieważ nie są one przenoszone.

Więc jeśli ELF32_R_TYPE (rel-> r_info) == R_PPC_ADDR32 mamy S + A, a wielkość słowo jest word32 więc otrzymujemy:

*(uint32_t *)(to_modify + rel->r_offset) = address_of_printf + rel->r_addend; 

... i udało nam się przeprowadzić do relokacji .

Nie mogę Ci pomóc, jeśli chodzi o #lo, #hi, itp. Oraz o rozmiarach słów takich jak low14. Nic nie wiem o PPC, ale linkowany plik PDF wydaje się wystarczająco rozsądny.

Nie wiem również o funkcjach pośredniczących. Zazwyczaj nie musisz o nich wiedzieć podczas łączenia (przynajmniej dynamicznie).

Nie jestem pewien, czy odpowiedziałem na wszystkie Twoje pytania, ale powinieneś być w stanie zobaczyć, co twój przykładowy kod robi co najmniej.

1

Spróbuj wkręcić w specyfikację ELF. Zajmuje około 60 stron i znacznie wyjaśnia rzeczy. Zwłaszcza część 2, ta o łączeniu.

+0

Lub , jeszcze lepiej, System V ABI (zarówno ogólny (gABI), jak i specyficzny dla architektury dodatek Znalazłem użyteczny: https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI (dla x86/x86-64) i http://www.sco.com/developers/gabi/ –