2012-05-04 31 views
6

Ho un problema con le deviazioni. Le deviazioni, come tutti sapete, possono muoversi solo tra 5 byte di spazio (cioè una chiamata "jmp" e un indirizzo a 4 byte). Per questo motivo è impossibile avere la funzione 'hook' in una classe (un metodo), non è possibile fornire il puntatore 'this' perché semplicemente non c'è abbastanza spazio (here's il problema è spiegato più a fondo). Quindi sono stato di brainstorming tutto il giorno per una soluzione, e ora voglio i tuoi pensieri sull'argomento quindi non inizierò un progetto di 3-5 giorni senza sapere se sarebbe possibile o meno.Funzioni dinamiche C++ e FULLY

Avevo inizialmente 3 obiettivi, volevo che le funzioni di "hook" fossero metodi di classe, volevo che l'intero approccio fosse orientato agli oggetti (nessuna funzione statica o oggetti globali) e, la parte peggiore/più difficile, essere completamente dinamico. Questa è la mia (in teoria) soluzione; con l'assemblaggio è possibile modificare le funzioni in fase di esecuzione (un esempio perfetto è qualsiasi metodo di deviazione). Quindi, dato che posso modificare le funzioni in modo dinamico, non dovrei anche essere in grado di crearle dinamicamente? Per esempio; Alloco memoria per, diciamo ~ 30 byte (tramite malloc/new). Non sarebbe possibile sostituire solo tutti i byte con numeri binari corrispondenti a diversi operatori di assembly (come 0xE9 è 'jmp') e quindi chiamare direttamente l'indirizzo (poiché conterrebbe una funzione)?

NOTA: conosco in anticipo il valore restituito e tutti gli argomenti per tutte le funzioni che voglio deviare, e poiché sto usando GCC, la convenzione thiscall è praticamente identica a quella _cdecl.

Quindi questo è il mio pensiero/prossima implementazione; Creo una classe 'Funzione'. Questo costruttore prende una quantità variabile di argomenti (tranne il primo argomento, che descrive il valore di ritorno della funzione obiettivo).

Ogni argomento è una descrizione degli argomenti che l'hook riceverà (la dimensione e se si tratta di un puntatore o meno). Quindi diciamo che voglio creare una classe Function per un int * RandomClass::IntCheckNum(short arg1);. Quindi dovrei solo fare questo: Function func(Type(4, true), Type(4, true), Type(2, false));. Dove "Tipo" è definito come Type(uint size, bool pointer). Quindi attraverso l'assemblea potrei creare dinamicamente la funzione (nota: si userebbe tutta la convenzione di chiamata _cdecl) poiché posso calcolare il numero di argomenti e la dimensione totale.

EDIT: Con l'esempio, Type(4, true) è il valore di ritorno (int *), il scond Type(4, true) è la RandomClass 'questo' puntatore e Type(2, false) descrive il primo argomento (breve arg1).

Con questa implementazione potrei facilmente avere i metodi di classe come callback, ma richiederebbe una grande quantità di codice assembly (che non sono nemmeno particolarmente esperto in). Alla fine, l'unica cosa non dinamica sarebbero i metodi nella mia classe di callback (che richiederebbe anche callback pre e post).

Quindi volevo sapere; È possibile? Quanto lavoro richiederebbe e sono sopra la mia testa qui?

EDIT: Mi dispiace se ho presentato tutto un po 'confuso, ma se c'è qualcosa che vuoi più accuratamente spiegato, chiedi!

EDIT2: Vorrei anche sapere se riesco a trovare da qualche parte i valori esadecimali per tutti gli operatori di assemblaggio? Una lista aiuterebbe una tonnellata! E/o se è possibile in qualche modo "salvare" asm (""); codice ad un indirizzo di memoria (che dubito fortemente).

+0

Perché usare le deviazioni? Non puoi usare una soluzione C++ pura come 'std :: function' o mi manca qualcosa? –

+0

Non come se potessi aiutarti solo a chiarire le cose. Vuoi una funzione riscrivibile in una classe? (Voglio dire, puoi cambiarli in fase di esecuzione) Se è così penso che (quando fatto) potrebbe eventualmente aprire enormi opportunità per la programmazione AI in C++. +1 – akaltar

+0

@akaltar Questo è noto come [programmazione genetica] (http://en.wikipedia.org/wiki/Genetic_programming) e in realtà non ha bisogno di funzioni riscrivibili. –

risposta

4

Quello che descrivi viene solitamente chiamato "thunking" e viene comunemente implementato. Storicamente, lo scopo più comune è stato la mappatura tra codice a 16 bit e a 32 bit (mediante la generazione automatica di una nuova funzione a 32 bit che chiama uno esistente a 16 bit o viceversa). Credo che alcuni compilatori C++ generino funzioni simili per regolare anche i puntatori di classe base ai puntatori delle sottoclassi nell'ereditarietà multipla.

Sembra certamente una soluzione valida al tuo problema e non prevedo alcun problema enorme. Assicurati di allocare la memoria con i flag necessari nel tuo sistema operativo per assicurarti che la memoria sia eseguibile (la maggior parte dei sistemi operativi moderni rilascia la memoria non eseguibile per impostazione predefinita).

Si possono trovare questo link utile, soprattutto se si lavora in Win32: http://www.codeproject.com/Articles/16785/Thunking-in-Win32-Simplifying-Callbacks-to-Non-sta

Per quanto riguarda trovare i valori esadecimali di operazioni di montaggio, il miglior riferimento che conosco è l'appendice al manuale del assembler NASM (e io non dirlo solo perché ho aiutato a scriverlo). C'è una copia disponibile qui: http://www.posix.nl/linuxassembly/nasmdochtml/nasmdoca.html

+0

Wow ottimi collegamenti! È stato davvero interessante leggere il processo di thunking (peccato però che fosse Win32). Ora scusami se sembro stupido, ma come detto prima, non sono particolarmente esperto nell'assemblaggio (conosco solo un po 'la sintassi AT & T) quindi ho dovuto chiedere dell'assemblatore NASM a cui ti riferivi. Ho 2 domande; tutti gli operatori ASM usano solo 1 byte? E in secondo luogo, dato che ci sono molti valori diversi specificati per ciascun operatore, a quale sono interessato? Immagino che dipenda dalla dimensione delle mie variabili, ma per 'push' ci sono 13 valori diversi, come faccio a sapere quale voglio? –

+1

Queste sono diverse varianti per diversi tipi di istruzione push (tipi di registro, valori immediati, riferimenti di memoria indiretti). La parte superiore della guida ha una descrizione di tutte le diverse modalità, quindi usala per calcolare quale vuoi, quindi guarda l'elenco per trovare il formato di istruzioni che ti serve. Supponiamo che tu voglia spingere EBX: è un reg32, quindi vuoi la seconda variante, che è "o32 50 + r". o32 è un prefisso dimensione operando, che viene ignorato se si sta eseguendo in codice a 32 bit; 50 + r è 50 hex più il codice per il registro (3, sono elencati in alto), quindi 53h è il tuo codice. – Jules

+1

In risposta alla tua prima domanda, non ci sono istruzioni lunghe più di un byte, e alcune istruzioni variano in dimensioni a seconda del contesto (vedi l'esempio PUSH sopra: il prefisso 'o32' non genera alcun codice in 32 bit modalità, tuttavia se si sta producendo codice a 16 bit, sarebbe un ulteriore 66h byte che appare all'inizio dell'istruzione). Tuttavia tutte le istruzioni più comuni sono single byte. – Jules