2013-05-30 24 views
13

TL; DRdelocalizzazioni Processing ELF - comprendere le relocs, i simboli, i dati di sezione e come funzionano insieme

ho cercato di rendere questo un breve domanda, ma è un problema complicato in modo che ha finito per essere a lungo. Se è possibile rispondere a qualsiasi parte di questo o fornire suggerimenti, suggerimenti o risorse o qualsiasi altra cosa, sarebbe estremamente utile (anche se non risolvono direttamente tutti i miei problemi). Sto sbattendo la testa contro il muro proprio ora. :)

Ecco i problemi specifici che sto riscontrando. Leggi sotto per maggiori informazioni.

  • Sto cercando indicazioni su come elaborare le voci di rilocazione e aggiornare i simboli non risolti nei dati della sezione. Ho semplicemente non capisco cosa fare con tutte le informazioni che ho tirato dalle delocalizzazioni e le sezioni, ecc
  • Sto anche sperando di capire che cosa sta succedendo quando il linker incontra delocalizzazioni. Cercare di implementare correttamente le equazioni di riposizionamento e utilizzare tutti i valori corretti nel modo corretto è incredibilmente difficile.
  • Quando incontro codici op, indirizzi e simboli, ecc., Ho bisogno di capire cosa fare con loro. Mi sento come se mi mancassero alcuni passaggi.
  • Mi sembra di non avere una buona conoscenza di come le voci della tabella dei simboli interagiscano con le delocalizzazioni. Come dovrei usare le informazioni di associazione, visibilità, valore e dimensione del simbolo?
  • Infine, quando invio il mio file con i dati risolti e le nuove voci di rilocazione utilizzate dall'eseguibile, i dati sono tutti errati. Non sono sicuro di come seguire tutte le delocalizzazioni e fornire tutte le informazioni necessarie. Qual è l'eseguibile che si aspetta da me?

Il mio approccio finora

Sto cercando di creare un file di rilocazione in un determinato formato proprietario [Non documentato] che è fortemente basata su ELF. Ho scritto uno strumento che accetta un file ELF e un file parzialmente collegato (PLF) e li elabora per generare il file rel completamente risolto. Questo file rel è usato per caricare/scaricare i dati secondo necessità per risparmiare memoria. La piattaforma è un PPC a 32 bit. Una ruga è che lo strumento è scritto per Windows in C#, ma i dati sono pensati per il PPC, quindi ci sono problemi divertenti di endian e simili a cui fare attenzione.

Ho cercato di capire come delocalizzazioni sono gestite quando viene utilizzato per risolvere i simboli non risolti e così via. Quello che ho fatto finora è copiare le sezioni rilevanti dal PLF e quindi per ciascuna sezione .rela corrispondente, analizzo le voci e cerco di sistemare i dati della sezione e generare nuove voci di rilocazione, se necessario. Ma questa è la mia difficoltà. Sono decisamente fuori dal mio elemento qui e questo genere di cose sembra essere tipicamente fatto da linker e caricatori quindi non ci sono molti buoni esempi su cui attingere. Ma ho trovato alcuni che sono stati di aiuto, tra cui THIS ONE.

Quindi, ciò che sta accadendo è:

  1. dati sezione Copia da PLF da utilizzare per il file rel. Mi interessa solo il .init (nessun dato), .text, .ctors, .dtors, .rodata, .data, .bss (nessun dato), e un'altra sezione personalizzata che stiamo usando.
  2. Iterare le sezioni .rela nel PLF e leggere le voci Elf32_Rela.
  3. Per ogni voce, estrarre i campi r_offset, r_info e r_addend ed estrarre le informazioni rilevanti da r_info (il simbolo e il tipo reloc).
  4. Dalla tabella dei simboli del PLF, posso ottenere symboloffset, symbolSection e symbolValue.
  5. Dall'ELF, ottengo l'indirizzo di caricamento di symbolSection.
  6. I calcoli int localAddress = (.relaSection.Offset + r_offset).
  7. Ottengo uint relocValue dal contenuto di symbolSection in r_offset.
  8. Ora ho tutte le informazioni di cui ho bisogno, quindi faccio uno switch sul tipo reloc e processo i dati. Questi sono i tipi che sostengo:
    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. Ora cosa ?? Devo aggiornare i dati della sezione e creare voci di rilocazione complementari. Ma non capisco cosa è necessario fare e come farlo.

Tutta la ragione per cui sto facendo questo è perché c'è un vecchio strumento non supportato obsoleto che non supporta l'utilizzo di sezioni personalizzate, che è un requisito fondamentale per questo progetto (per ragioni di memoria). Abbiamo una sezione personalizzata che contiene un mucchio di codice di inizializzazione (per un totale di circa un meg) che vogliamo scaricare dopo l'avvio. Lo strumento esistente ignora tutti i dati in quella sezione.

Quindi, mentre creare il nostro strumento che supporta le sezioni personalizzate è l'ideale, se ci sono idee brillanti per un altro modo per raggiungere questo obiettivo, sono tutto orecchie! Abbiamo vagabondato sull'idea di utilizzare la sezione .dtor per i nostri dati poiché è quasi vuota comunque. Ma questo è disordinato e potrebbe non funzionare comunque se impedisce un arresto pulito.


Relocations più codice di esempio

Quando ho elaborare i trasferimenti, sto lavorando al largo delle equazioni e informazioni che si trovano nella documentazione ABI HERE (circa sezione 4.13, pagina 80ish) così come una serie di altri esempi di codice e post di blog che ho trovato. Ma è tutto così confuso e non molto esplicitato e tutto il codice che ho trovato fa le cose in modo leggermente diverso.

Per esempio,

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

Quindi quando vedo questo tipo di codice, come lo decifro?

Ecco un esempio (da 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; 

Ecco alcuni da un altro esempio (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; 

E un altro esempio (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; 

voglio davvero per capire la magia che questi ragazzi stanno facendo g qui e perché il loro codice non è sempre lo stesso. Penso che parte del codice stia facendo supposizioni sul fatto che i dati fossero già mascherati correttamente (per i rami, ecc.) E che alcuni dei codici non lo fossero. Ma non capisco niente di tutto ciò.


Dopo i simboli/dati/delocalizzazioni, ecc

Quando guardo i dati in un HexEditor, vedo un gruppo di "48 00 00 01" in tutto. Ho capito che questo è un codice operativo e deve essere aggiornato con le informazioni di trasferimento (questo è specifico per il ramo "bl" e il collegamento), tuttavia il mio strumento non funziona sulla maggior parte di essi e su quelli che faccio aggiornamento hanno valori errati in loro (rispetto ad un esempio fatto da uno strumento obsoleto). Chiaramente mi manca qualche parte del processo.

Oltre ai dati della sezione, sono presenti voci di spostamento aggiuntive che devono essere aggiunte alla fine del file rel. Si tratta di trasferimenti interni ed esterni, ma non ho ancora capito molto di questi. (qual è la differenza tra i due e quando si utilizza uno o l'altro?)

Se si guarda vicino alla fine di this file alla funzione RuntimeDyldELF::processRelocationRef, verranno visualizzate alcune voci di rilocazione. Fanno anche funzioni stub. Sospetto che questo sia l'anello mancante per me, ma è chiaro come il fango e non lo seguo nemmeno un po '.

Quando invio i simboli in ciascuna voce di rilocazione, ognuno di essi ha una rilegatura/visibilità [Globale/Debole/Locale] [Funzione/Oggetto] e un valore, una dimensione e una sezione. So che la sezione è dove si trova il simbolo, e il valore è l'offset del simbolo in quella sezione (o è l'indirizzo virtuale?). La dimensione è la dimensione del simbolo, ma è importante? Forse il globale/debole/locale è utile per determinare se si tratta di un trasferimento interno o esterno?

Forse questa tabella di riposizionamento di cui sto parlando è in realtà una tabella di simboli per il mio file rel? Forse questa tabella aggiorna il valore del simbolo da essere un indirizzo virtuale ad essere un offset di sezione (dato che questo è ciò che il valore è in file rilocabili e la tabella dei simboli nel PLF è fondamentalmente in un eseguibile)?


Alcune risorse:

  1. blog sulle delocalizzazioni: http://eli.thegreenplace.net/2011/08/25/load-time-relocation-of-shared-libraries/
  2. menzioni codici operativi alla fine: http://wiki.netbsd.org/examples/elf_executables_for_powerpc/
  3. mia correlata domanda senza risposta: ELF Relocation reverse engineering

Whew! Questa è una bestia di una domanda. Complimenti se hai fatto fin qui. :) Grazie in anticipo per qualsiasi aiuto tu possa darmi.

+0

possibile duplicato di [Concept of relocation] (http://stackoverflow.com/questions/16385826/concept-of-relocation) Esempio minimo dettagliato su: http://stackoverflow.com/a/30507725/895245 –

risposta

14

Mi sono imbattuto in questa domanda e ho pensato che meritasse una risposta.

Avere elf.h a portata di mano. Puoi trovarlo su internet.

Ogni sezione RELA contiene una serie di voci Elf32_Rela come sapete, ma è anche legata a un'altra determinata sezione. r_offset è un offset in quell'altra sezione (in questo caso - it works differently for shared libraries). Troverete che le intestazioni di sezione hanno un membro chiamato sh_info. Questo ti dice quale sezione è. (È un indice nella tabella delle intestazioni di sezione come ci si aspetterebbe.)

Il "simbolo", che hai ottenuto da r_info, è in realtà un indice in una tabella di simboli che risiede in un'altra sezione. Cerca il membro sh_link nell'intestazione della sezione RELA.

La tabella dei simboli indica il nome del simbolo che si sta cercando, nella forma del membro st_name di Elf32_Sym. st_name è un offset in una sezione di stringhe. Quale sezione, si ottiene dal membro sh_link dell'intestazione della sezione della tabella dei simboli. Scusa se questo ti confonde.

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; 

Diciamo che stiamo lavorando con il numero di riposizionamento 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; 

Trovare l'indirizzo di tale simbolo (diciamo che symbol_name == "printf"). Il valore finale entrerà in (to_modify + rel-> r_offset).

Come per la tabella alle pagine 79-83 del pdf che hai collegato, ci dice cosa mettere a quell'indirizzo e quanti byte scrivere. Ovviamente l'indirizzo che abbiamo appena ricevuto (di printf in questo caso) è parte della maggior parte di essi. Corrisponde alla S nelle espressioni.

r_addend è solo A. A volte il compilatore deve aggiungere una costante statica a un reloc credo.

B è l'indirizzo di base dell'oggetto condiviso o 0 per i programmi eseguibili perché non vengono spostati.

Quindi, se ELF32_R_TYPE (rel-> r_info) == R_PPC_ADDR32 abbiamo S + A, e la dimensione della parola è word32 in modo che otterrebbe:

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

... e abbiamo eseguito con successo una delocalizzazione .

Non posso aiutarti quando si tratta di #lo, #hi, ecc. E le dimensioni delle parole come low14. Non so nulla di PPC, ma il pdf collegato sembra abbastanza ragionevole.

Inoltre, non conosco le funzioni di stub. Normalmente non hai bisogno di sapere chi è collegato (almeno dinamicamente).

Non sono sicuro di aver risposto a tutte le tue domande, ma dovresti essere in grado di vedere che cosa fa il codice di esempio almeno adesso.

1

Cercare di eseguire il llob delle specifiche ELF. Ci vogliono circa 60 pagine, e chiarisce molto le cose. Soprattutto la parte 2, quella sul collegamento.

+0

Oppure , ancora meglio, System V ABI (sia generico (gABI) che integratore specifico per l'architettura. Ho trovato utile: https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI (per x86/x86-64) e http://www.sco.com/developers/gabi/ –