[c++/asm] inhoud registers laten zien

Pagina: 1
Acties:

  • cenix
  • Registratie: September 2001
  • Laatst online: 17-05 08:56
Na veel zoekwerk ben ik er nog steeds niet uit, maar is er een manier om de inhoud van de verschillende registers (eax, ebx, e.d.) te laten zien via code?

voorbeeldje (msdev registers window)
code:
1
2
3
4
 EAX = 002F10A0 EBX = 7FFDF000
 ECX = 00000001 EDX = 002F1118
 ESI = 00000000 EDI = 00000000
 EIP = 00401010

  • LeX-333
  • Registratie: Maart 2004
  • Laatst online: 21-11-2016
Voor GCC en de Intel compiler:

code:
1
2
3
4
5
6
7
8
9
10
11
unsigned long eaxr, ebxr, ecxr, edxr;
asm volatile ("mov %%eax, %0 \n\t"
              "mov %%ebx, %1 \n\t"
              "mov %%ecx, %2 \n\t"
              "mov %%edx, %3 \n\t"
              : "=m" (eaxr),
                "=m" (ebxr),
                "=m" (ecxr),
                "=m" (edxr)
              :
              );


Voor de microsoft compiler gaat het natuurlijk weer een beetje anders (uiteraard):
code:
1
2
3
4
5
6
7
8
unsigned long eaxr, ebxr, ecxr, edxr;
__asm
{
  MOV eaxr, EAX
  MOV ebxr, EBX
  MOV ecxr, ECX
  MOV edxr, EDX
}


Ik heb dit zo uit mijn hoofd ingetypt, dus niet getest, maar dit zou zo moeten werken :)

Too many people, making too many problems


  • MSalters
  • Registratie: Juni 2001
  • Laatst online: 09-04 22:08
Waarom? De vraag is vooral belangrijk voor EIP. Die verandert natuurlijk als je code uitvoert om 'm te laten zien.

Man hopes. Genius creates. Ralph Waldo Emerson
Never worry about theory as long as the machinery does what it's supposed to do. R. A. Heinlein


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 23:27

Tomatoman

Fulltime prutser

Wat wil je precies bereiken? In sommige IDE's (in ieder geval Borland C++ Builder) kun je tijdens het debuggen prachtig zien wat de waarde van alle registers is. In C++ Builder heb je daarvoor het 'CPU window'.

Een goede grap mag vrienden kosten.


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

offtopic:
Ahh...eindelijk eens iets waar de MS compiler iets erg goed doet. Inline assembler werkt zo veel makkelijker daar....kudos, mag ook wel eens gezegd worden.

  • LeX-333
  • Registratie: Maart 2004
  • Laatst online: 21-11-2016
tomatoman schreef op 23 augustus 2004 @ 00:21:
Wat wil je precies bereiken? In sommige IDE's (in ieder geval Borland C++ Builder) kun je tijdens het debuggen prachtig zien wat de waarde van alle registers is. In C++ Builder heb je daarvoor het 'CPU window'.
Het is inderdaad wel interresant om te weten wat je er mee wilt :) Ikzelf gebruik liever geen IDE, die dingen zijn soms zo traag als dikke stront (Helemaal die Java IDE's, maar goed Java is uberhaupt traag). Zelf gebruik ik liever een tekst editor en een makefile. Als je dan ff wilt weten wat de waarde van een bepaald register is, is dit het gemakkelijkst.
Zoijar schreef op 23 augustus 2004 @ 03:06:
offtopic:
Ahh...eindelijk eens iets waar de MS compiler iets erg goed doet. Inline assembler werkt zo veel makkelijker daar....kudos, mag ook wel eens gezegd worden.
offtopic:
Forget it! Als je inline assembler gebruik doe je dit meestal niet omdat het makkelijk is, maar om de hoogst mogelijke performance te halen. Bij GCC is het mogelijk om aan de compiler te vertellen welke input, output en clobber registers gereserveerd moeten worden. Hierdoor wordt de assembly output van GCC zeer efficient, bij de microsoft compiler valt dit nogal tegen. Tevens is het mogelijk om GCC een oprtimalistatieslag te laten doen over je assembly, hierdoor kan je code nog sneller worden. Dit doe je door het volatile keyword weg te laten en kan je dus per assembly blok specificeren. Feit is dat als je veel kleine asm blokken gebruik tussen bijvoorbeeld for en do loops, GCC vele malen sneller is dan microsoft's compiler.

Too many people, making too many problems


  • Elijan9
  • Registratie: Februari 2004
  • Laatst online: 20-05 13:00
cenix schreef op 22 augustus 2004 @ 20:20:
Na veel zoekwerk ben ik er nog steeds niet uit, maar is er een manier om de inhoud van de verschillende registers (eax, ebx, e.d.) te laten zien via code?

voorbeeldje (msdev registers window)
code:
1
2
3
4
 EAX = 002F10A0 EBX = 7FFDF000
 ECX = 00000001 EDX = 002F1118
 ESI = 00000000 EDI = 00000000
 EIP = 00401010
Die EIP is wat lastig. Hoe debuggers dit doen, is meestal door breakpoints te zetten tot waar je wilt steppen/runnen. Hiervoor wordt meestal een deel van de code gepatched (overschreven) door een "int 3" (de originele code wordt bijgehouden).
Komt de code op een int 3 terecht, dan wordt de EIP (dacht ik) op de stack gegooid. De eerstvolgende instructie is meestal een pusha, waarbij alle registers op de stack worden gezet, vervolgens kun je veilig van de juiste plekken op de stack de registerwaarden (inclusief de EIP). Ergens aan het einde van deze intterrupt wordt de oorspronkelijke code (die gepatched was met int 3) alsnog uitgevoerd enzovoort, ik zal niet nog meer in details vallen, het idee is denk ik wel duidelijk.

Waarschijnlijk wil jij geen code patchen en alles wat ebij kont kijken om breakpoints te gebruiken. Een functiecall in je eigen code plaatsen zou ook prima kunnen, zorg dan wel dat deze geprototyped is als een standaard-C functiecall. De functie zou grotendeels uit assembly moeten bestaan, waarschijnlijk is het beter meteen de hele functie in assembly te schrijven, omdat afhankelijk van de compiler en comp.-opties, ook locale variabelen al op de stack worden gezet en er wel of geen gebruik gemaakt wordt van een framepointer, enzovoort. Bovendien moet je in een C-functie dan ook er goed mee rekening houden dat de waarde van EAX ook de daadwerkelijke returnwaarde van de functie wordt...

Ik heb hier even niet de details over waar op welke volgorde de registers terecht komen op de stack, en welke wel of niet erop terecht komen, maar ik hoef ook niet alles weg te geven voor je... In elk geval: pusha aan het begin, de oorspronkelijke registerinhouden aflezen van de stack, en aan het aan eind van de functie een popa om de oorspronkelijke registerwaarden terug te zetten.

[ Voor 7% gewijzigd door Elijan9 op 23-08-2004 11:02 . Reden: vooral typo's ]

War is when the young and stupid are tricked by the old and bitter into killing each other. - Niko Bellic


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

LeX-333 schreef op 23 augustus 2004 @ 09:42:
offtopic:
Forget it! Als je inline assembler gebruik doe je dit meestal niet omdat het makkelijk is, maar om de hoogst mogelijke performance te halen. Bij GCC is het mogelijk om aan de compiler te vertellen welke input, output en clobber registers gereserveerd moeten worden. Hierdoor wordt de assembly output van GCC zeer efficient, bij de microsoft compiler valt dit nogal tegen. Tevens is het mogelijk om GCC een oprtimalistatieslag te laten doen over je assembly, hierdoor kan je code nog sneller worden. Dit doe je door het volatile keyword weg te laten en kan je dus per assembly blok specificeren. Feit is dat als je veel kleine asm blokken gebruik tussen bijvoorbeeld for en do loops, GCC vele malen sneller is dan microsoft's compiler.
offtopic:
Voor zover ik weet doet msvc dat zelf. De compiler kan prima zelf kijken welke registers je gebruikt, en welke je clobbered etc. In ieder geval had ik laatst een asm stukje, en vc7 en gcc maakten er exact dezelfde (optimized) code van. Alleen moest ik in gcc helemaal nadenken welke registers ik op moest geven, en deed vc dat helemaal zelf.

  • cenix
  • Registratie: September 2001
  • Laatst online: 17-05 08:56
Ik doe dit vooral om de registers at runtime te volgen, en dit in een file te kunnen opslaan en dus terug kunnen lezen, zonder dat ik de hele tijd van scherm moet wisselen e.d.
Ook als het programma niet in debug-mode draait wil ik dit kunnen volgen.

ik heb idd gelezen dat de EIP lastig is, maar ook heb ik ietswat aan info daarover gevonden, zie http://www.masmforum.com/....php?t=3681&highlight=eip

Ik ga eens aan het testen, bedankt iig zover

  • LeX-333
  • Registratie: Maart 2004
  • Laatst online: 21-11-2016
Zoijar schreef op 23 augustus 2004 @ 11:38:
[...]

offtopic:
Voor zover ik weet doet msvc dat zelf. De compiler kan prima zelf kijken welke registers je gebruikt, en welke je clobbered etc. In ieder geval had ik laatst een asm stukje, en vc7 en gcc maakten er exact dezelfde (optimized) code van. Alleen moest ik in gcc helemaal nadenken welke registers ik op moest geven, en deed vc dat helemaal zelf.
offtopic:
Bij simpele ASM code is dat inderdaad mogelijk. Echter zodra je via (bijvoorbeeld) een call een ander blok code aanroept, kan de compiler nooit achterhalen welke registers gewijzigd worden. Tevens zijn stack-frame modificaties al helemaal lastig te achterhalen, bovendien kun je run-time dingen doen die compile-time niet te achterhalen zijn. Als laatste ontbreekt ook nog de functionaliteit van volatile, dus heb je weinig controle over de optimalisatie van je ASM code.


Ok, hier een klein voorbeeldje van hoe GCC en MSVC hun assembly optimaliseren. Dit is een stukje code dat het CR0 register uitleest, vooral handig voor kernel freaks O-) Beide compilers krijgen de opdracht hun code maximaal te optimaliseren (dus ook microsoft's compiler) behalve door functies te inlinen (dan wordt de ASM zo'n zootje :) )
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

void output(int val)
{
  std::cout << "output: " << val << std::endl;
}

int main(void)
{
  int value;

  #ifdef _MSC_VER
    __asm
    {
      MOV EAX, CR0
      MOV value, EAX
    }
  #else
    asm volatile ("mov %%cr0, %%eax \n\t"
                  : "=a" (value)
                  :
                  );
  #endif

  output(value);

  return 0;
}
Bij GCC wordt "MOV value, EAX" vervangen door ""=a" (value)"


Dit is wat GCC er van maakt (3 instucties):
code:
1
2
3
4
5
6
/APP
    mov %cr0, %eax 
    
/NO_APP
    movl    %eax, (%esp)
    call    __Z6outputi
CR0 wordt in EAX geplaatst, hierna wordt EAX op de stacktop geplaatst ("movl %cr0, (%esp)" is geen geldige instructie). Dan wordt de functie output aangeroepen.


En dit is wat MSVC er van maakt (6 instucties, 2x zoveel dus!):
code:
1
2
3
4
5
6
7
8
9
; Line 15
    mov eax, cr0
; Line 16
    mov DWORD PTR _value$[ebp], eax
; Line 25
    mov eax, DWORD PTR _value$[ebp]
    push    eax
    call    ?output@@YAXH@Z             ; output
    add esp, 4
CR0 wordt in EAX geplaatst, hierna wordt EAX in de variable value opgeslagen. Dan wordt de variabele value weer in EAX gezet |:( en daarna op de stack gepushed. Dan wordt de functie output aangeroepen, en in tegenstelling tot GCC de stack weer vrijgemaakt.

Too many people, making too many problems


  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Ja daar doet vc een beetje vreemd idd; heb je gelijk in.

  • Elijan9
  • Registratie: Februari 2004
  • Laatst online: 20-05 13:00
Mijn voorstel zou zijn, voor gcc met "intel style assembly" ( -masm=intel):
C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <inttypes.h>

void dumpRegisters(void);

__asm(
"dumpRegisters:\n"  /* "C" call to dumpRegisters pushes EIP */
"   pushad\n"       /* pushes EAX, EDX, ECX, EBX, ESP(orig), EBP, ESI, EDI */
"   pushfd\n"       /* pushes EFLAGS */
"   call dumpRegisterHelper\n"
"   popfd\n"
"   popad\n"
"   ret\n");

void dumpRegisterHelper(
            uint32_t eflags_val, 
            uint32_t edi_val,
            uint32_t esi_val,
            uint32_t ebp_val,
            uint32_t esp_4_val,
            uint32_t ebx_val,
            uint32_t edx_val,
            uint32_t ecx_val,
            uint32_t eax_val,
            uint32_t eip_val)
{
    printf("EAX:\t%08X\tEBX:\t%08X\n", eax_val, ebx_val);
    printf("ECX:\t%08X\tEDX:\t%08X\n", ecx_val, edx_val);
    printf("ESI:\t%08X\tEDI:\t%08X\n", esi_val, edi_val);
    printf("ESP:\t%08X\tEBP:\t%08X\n", (esp_4_val + 4), ebp_val);
    printf("EIP:\t%08X\tEFLAGS:\t%08X\n", eip_val, eflags_val);
}

int main(void)
{
    __asm("call dumpRegisters");

    return 0;
}


Voor het ESP register geldt dat de waarde op het moment van de PUSHA met 4 verschilt van de waarde die deze zal hebben, maar dat is logisch (call naar dumpRegisters). Daar moet je dus ook even rekening mee houden als je waarden weg wilt schrijven naar ESP.

Naar mijn mening is een "ret" instructie de ENIGE manier om fatsoenlijk de EIP een waarde toe te kennen, zonder een ander register te verminken. Het valt me dan ook zwaar tegen dat niemand op het dat masmforum daarmee komt...
Dat ik __asm("call dumpRegisters"); gebruik in plaats van gewoon direct "dumpRegisters();" heeft ook een reden: anders kan de compiler er niet van uit gaan dat de waarde in o.a. EAX intact blijft tijdens de uitvoering van de functie. Ik hoop trouwens maar dat elke compiler er op deze manier wel vanuit wil gaan ;)

[ Voor 10% gewijzigd door Elijan9 op 23-08-2004 15:10 . Reden: verduidelijkend commentaar ]

War is when the young and stupid are tricked by the old and bitter into killing each other. - Niko Bellic


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 23-05 18:13
Je kunt de EIP waarde van net voor de call naar dumpRegisters toch gewoon uit je call stack frame vissen?

  • Elijan9
  • Registratie: Februari 2004
  • Laatst online: 20-05 13:00
Soultaker schreef op 23 augustus 2004 @ 14:32:
Je kunt de EIP waarde van net voor de call naar dumpRegisters toch gewoon uit je call stack frame vissen?
Doe ik toch ook :? Beweer alleen dat de enige manier om naar EIP te schrijven zonder een ander register te verminken, gedaan dient te worden via een RET.

[ Voor 40% gewijzigd door Elijan9 op 23-08-2004 14:51 ]

War is when the young and stupid are tricked by the old and bitter into killing each other. - Niko Bellic


  • Soultaker
  • Registratie: September 2000
  • Laatst online: 23-05 18:13
Ah inderdaad, dat had ik even verkeerd gelezen.... maar als je ret wil gebruiken moet je wel je stackframe (of je framepointer) verprutsen!

[ Voor 48% gewijzigd door Soultaker op 23-08-2004 15:41 ]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-05 23:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Elijan9 schreef op 23 augustus 2004 @ 14:47:
[...]

Doe ik toch ook :? Beweer alleen dat de enige manier om naar EIP te schrijven zonder een ander register te verminken, gedaan dient te worden via een RET.
of jmp

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Elijan9
  • Registratie: Februari 2004
  • Laatst online: 20-05 13:00
Dan moet je dus wel zo'n jmp tijdens runtime in elkaar patchen, want in deze situatie wil je dus dat een gebruiker runtime een waarde voor eip kan opgeven... (Daar is mijn voorbeeldcode nog niet voor toereikend)
Soultaker schreef op 23 augustus 2004 @ 15:40:
maar als je ret wil gebruiken moet je wel je stackframe (of je framepointer) verprutsen!
Door de EIP te veranderen verpruts je mogelijk de stackframe. Maar dan ligt de oorzaak bij de nieuwe waarde van EIP, niet bij het gebruik van RET.
Verprutsen hoeft niet om ret te kunnen gebruiken, een ret is niets anders dan een "pop eip". En pops horen in evenwicht te zijn met pushes. (En een call is niets anders dan een push eip : jmp newvalue)
Je kunt gewoon een waarde wegschrijven naar de stackoffset van het returnadres. Tegen de tijd dat er ge-returned wordt, krijgt de eip automagisch naar de nieuwe waarde. Daartussen kunnen alle andere registers teruggezet worden.

[ Voor 4% gewijzigd door Elijan9 op 23-08-2004 16:50 ]

War is when the young and stupid are tricked by the old and bitter into killing each other. - Niko Bellic


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-05 23:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Elijan9 schreef op 23 augustus 2004 @ 16:48:
Dan moet je dus wel zo'n jmp tijdens runtime in elkaar patchen, want in deze situatie wil je dus dat een gebruiker runtime een waarde voor eip kan opgeven... (Daar is mijn voorbeeldcode nog niet voor toereikend)
uhm, jmp [reg32] en jmp [mem32] bijvoorbeeld ?

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Elijan9
  • Registratie: Februari 2004
  • Laatst online: 20-05 13:00
En welk niet beschikbaar register ga je daarvoor gebruiken :X Of mis je het hele idee eventjes, om alle registers intact te kunnen houden ;)
en jmp [mem32] bijvoorbeeld ?
Die kan inderdaad wel, die was ik zelf eerlijk gezegd vergeten.

Maar eigenlijk, om de EIP ergens te achterhalen, is een call toch de wel beste mogelijkheid om de EIP te verkrijgen voor de volgende instructie, toch? (Scheelt om zijn minst een hoop rekenwerk.) Een ret gebruiken om de (mogelijk veranderde) waarde voor EIP van de stack te halen is dan wel meteen zo logisch.
Maar "jmp [mem32]" zou inderdaad kunnen, moet je wel een geheugenplekje reserveren (ach ja). Het wordt er in elk geval niet leesbaarder op met een jmp [mem32]. De EIP zal trouwens vooral gelezen worden.

War is when the young and stupid are tricked by the old and bitter into killing each other. - Niko Bellic


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 22-05 23:07

.oisyn

Moderator Devschuur®

Demotivational Speaker

Elijan9 schreef op 23 augustus 2004 @ 23:52:

En welk niet beschikbaar register ga je daarvoor gebruiken :X Of mis je het hele idee eventjes, om alle registers intact te kunnen houden ;)
daar heb je een punt ;)
Maar "jmp [mem32]" zou inderdaad kunnen, moet je wel een geheugenplekje reserveren (ach ja)
die heb je al, waar staat ie anders? :)
Het wordt er in elk geval niet leesbaarder op met een jmp [mem32]
Het ligt natuurlijk wel een beetje aan je stack. Voor de ret is een geldige stack nodig (die je meestal natuurlijk wel hebt), maar voor hetzelfde geldt is de esp bogus of is de stack op dat moment niet toegankelijk door de huidige privilege level (waar je natuurlijk geen last van hebt in normale user-mode apps). De jmp is dan essentieel.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.

Pagina: 1