kicsi assembly meg gdb bénázás

Ifjoncabb kollégákkal beszélgetve szóba került, hogy még nem láttak assembly kódot, ezért gondoltam mutatok nekik, mégiscsak tudják mi fán terem :)

 

Az volt az ötletem, hogy nézzünk meg egy HelloWorldöt asm-ben meg javaban, hogy lássák tényleg ilyen ilyen obskúrus izék lesznek abból amit ők írkálnak. Na a Javaról hamar letettem, hogy gdb-vel nézegessek egy System.out.println() -t, próbáljuk meg C-ben, de itt is ráballagtam a banánhéjra.

 

Az asm progi:

.global _start

.text

_start:
	mov $1, %rax
	mov $1, %rdi
	mov $message, %rsi
	mov $13, %rdx
	syscall

	mov $60, %rax
	xor %rdi, %rdi
	syscall

.data

message:
	.ascii "Hello World!\n"

 

A C progi:

#include <stdio.h>

int main(void) {
	puts("Hello World!\n");
	return 0;
}

gcc -Wall -g hello.c -o helloc

 

gdb helloc

 

aztán breakpoint a puts-ra és had szaladjon!

...

Reading symbols from helloc...
(gdb) b puts
Breakpoint 1 at 0x1050
(gdb) r
Starting program: /sandbox/asm/helloc 

This GDB supports auto-downloading debuginfo from the following URLs:
  <https://debuginfod.ubuntu.com>
Enable debuginfod for this session? (y or [n]) 
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, __GI__IO_puts (str=0x555555556004 "Hello World!\n") at ./libio/ioputs.c:33
33	./libio/ioputs.c: No such file or directory.
(gdb) disassemble
Dump of assembler code for function __GI__IO_puts:
Address range 0x7ffff7c7af40 to 0x7ffff7c7b166:
=> 0x00007ffff7c7af40 <+0>:	endbr64
   0x00007ffff7c7af44 <+4>:	push   %r14
   0x00007ffff7c7af46 <+6>:	push   %r13
   0x00007ffff7c7af48 <+8>:	push   %r12
   0x00007ffff7c7af4a <+10>:	push   %rbp
   0x00007ffff7c7af4b <+11>:	mov    %rdi,%rbp
   0x00007ffff7c7af4e <+14>:	push   %rbx
   0x00007ffff7c7af4f <+15>:	sub    $0x10,%rsp
   0x00007ffff7c7af53 <+19>:	call   0x7ffff7c224c0 <*ABS*+0xa6ff0@plt>
   0x00007ffff7c7af58 <+24>:	mov    0x17aec1(%rip),%r13        # 0x7ffff7df5e20
   0x00007ffff7c7af5f <+31>:	mov    %rax,%rbx
   0x00007ffff7c7af62 <+34>:	mov    0x0(%r13),%r12
   0x00007ffff7c7af66 <+38>:	testl  $0x8000,(%r12)
   0x00007ffff7c7af6e <+46>:	je     0x7ffff7c7b040 <__GI__IO_puts+256>
   0x00007ffff7c7af74 <+52>:	mov    %r12,%rdi
   0x00007ffff7c7af77 <+55>:	mov    0xc0(%rdi),%eax
   0x00007ffff7c7af7d <+61>:	test   %eax,%eax
   0x00007ffff7c7af7f <+63>:	jne    0x7ffff7c7b096 <__GI__IO_puts+342>
   0x00007ffff7c7af85 <+69>:	movl   $0xffffffff,0xc0(%rdi)
   0x00007ffff7c7af8f <+79>:	mov    0xd8(%rdi),%r14
   0x00007ffff7c7af96 <+86>:	lea    0x177d63(%rip),%rdx        # 0x7ffff7df2d00 <_IO_printf_buffer_as_file_jumps>

...

 

Nyilván van a glibc-ben egy csomó más fontos sallang is, de azt furcsállom, hogy egy darab kernel hívás (syscall vagy call 0x80) nincs benne. Persze máshol biztos van, csak így, hogy kb. 0 az átfedés a két kód között, nem túl szemléletes :)

 

Segítsetek kérlek mit, hogy írjak másként, hogy összehasonlítható legyen?

 

Köszi!

Hozzászólások

Szerkesztve: 2024. 11. 20., sze – 16:59

Nyilván nem fogsz itt syscallt látni, hiszen magát a kernelt a glibc hívja meg, miután platformspecifikus módon implementálja a printf()-et, meg minden más libc függvényt. Ezek az absztrakciós rétegek már csak ilyenek, ez az ára a hordozhatóságnak.

Például a puts belső implementációja is ilyen:

https://sourceware.org/git/?p=glibc.git;a=blob;f=libio/ioputs.c;h=7f719…

Ez ugye láthatólag a belső _IO_sputn-t hívja meg.

Az meg egy makró: https://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libioP.h;h=a83a4…

Ami megint csak egy makró: https://sourceware.org/git/?p=glibc.git;a=blob;f=libio/libioP.h;h=a83a4…

Nem olyan egyszerű ám a libc, mint az ember gondolná.

Szerkesztve: 2024. 11. 20., sze – 17:21

de azt furcsállom, hogy egy darab kernel hívás (syscall vagy call 0x80) nincs benne

Persze hogy nincs, mert a glibc egy bloated foshalmaz, nem véletlenül írták meg a MUSL-t helyette.

Segítsetek kérlek mit, hogy írjak másként, hogy összehasonlítható legyen?

Használj "-static" kapcsolót (ekkor kimarad a dinamikus linkelésből eredő plt trampoline és a glibc szimbólumok is bekerülnek a dwarf-ba a "-g" hatására), valamint első körben "objdump", az szépen megmondja, felesleges a gdb.

gcc -Wall -g hello.c -o helloc --static
objdump -xd helloc

Egyből látszik is, hogy a fő main függvény:

0000000000401845 :
  401845:       55                      push   %rbp
  401846:       48 89 e5                mov    %rsp,%rbp
  401849:       48 8d 05 c0 97 07 00    lea    0x797c0(%rip),%rax        # 47b010 <__rseq_flags+0xc>
  401850:       48 89 c7                mov    %rax,%rdi
  401853:       e8 f8 30 00 00          call   404950 <_IO_puts>
  401858:       b8 00 00 00 00          mov    $0x0,%eax
  40185d:       5d                      pop    %rbp
  40185e:       c3                      ret
  40185f:       90                      nop

Tehát az _IO_puts nevű függvényt hívja valójában. Ennek a kódja:

0000000000404950 <_IO_puts>:
  404950:       f3 0f 1e fa             endbr64
  404954:       55                      push   %rbp
  404955:       48 89 e5                mov    %rsp,%rbp
  404958:       41 56                   push   %r14
  40495a:       41 55                   push   %r13
  40495c:       41 54                   push   %r12
  40495e:       49 89 fc                mov    %rdi,%r12
  404961:       53                      push   %rbx
  404962:       48 83 ec 10             sub    $0x10,%rsp
  404966:       e8 35 c7 ff ff          call   4010a0 <_init+0xa0>
  40496b:       4c 8b 2d 3e 1d 0a 00    mov    0xa1d3e(%rip),%r13        # 4a66b0 
  404972:       48 89 c3                mov    %rax,%rbx
  404975:       41 f6 45 01 80          testb  $0x80,0x1(%r13)
  40497a:       0f 84 d0 00 00 00       je     404a50 <_IO_puts+0x100>
  404980:       4c 89 ef                mov    %r13,%rdi
  404983:       8b 87 c0 00 00 00       mov    0xc0(%rdi),%eax
  404989:       85 c0                   test   %eax,%eax
  40498b:       0f 85 af 00 00 00       jne    404a40 <_IO_puts+0xf0>
  404991:       c7 87 c0 00 00 00 ff    movl   $0xffffffff,0xc0(%rdi)
  404998:       ff ff ff 
  40499b:       4c 8b b7 d8 00 00 00    mov    0xd8(%rdi),%r14
  4049a2:       48 8d 15 d7 ef 09 00    lea    0x9efd7(%rip),%rdx        # 4a3980 <__io_vtables>
  4049a9:       4c 89 f0                mov    %r14,%rax
  4049ac:       48 29 d0                sub    %rdx,%rax
  4049af:       48 3d 2f 09 00 00       cmp    $0x92f,%rax
...

Ez már pont úgy néz ki, mint amit a gdb-ben látsz, és a cimkékből az is látszik, hogy itt bizony virtuális metódusokkal történő gányolás esete forog fenn.

Most, hogy már tudjuk, nem is "puts", hanem "_IO_puts" a függvény neve, a glibc forrásában a C kódra rákeresve ezt kapjuk:

int
_IO_puts (const char *str)
{
  int result = EOF;
  size_t len = strlen (str);
  _IO_acquire_lock (stdout);

  if ((_IO_vtable_offset (stdout) != 0
       || _IO_fwide (stdout, -1) == -1)
      && _IO_sputn (stdout, str, len) == len
      && _IO_putc_unlocked ('\n', stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (stdout);
  return result;
}

És valóban sehol egy syscall, csak még további bloated hivások függvénycímeket tartalmazó táblákból... (megjegyzem, a file descriptorhoz tartozó átirányítás NEM a glibc dolga, hanem a kernelé, ezért itt erre vtable-t használni szimplán faszság).

Nem olyan egyszerű ám a libc, mint az ember gondolná.

Dehogynem, csak a glibc egy fos.

Az a baj, hogy szép, hogy a musl soványabb, és nem olyan elhízott, mint a glibc, de sajnos van bőven szoftver, ami glibc nélkül nem fog menni. Az is igaz, hogy bloat a glibc, de azért attól is függ, hogy mihez képest nézzük. Ha a Javához, meg a hőn utált Rust-odhoz képest nézzük, azokhoz képest akár soványkának is lehetne nevezni.

The world runs on Excel spreadsheets. (Dylan Beattie)

a hőn utált Rust-odhoz képest nézzük

Én nem utálom a Rust-ot. Nem a kedvenc nyelvem, látom a hosszútávú buktatóit, de nem utálom. Amit utálok, az a sok nagypofájú balfasz Rustafári, akik azt hiszik, ők szarták a spanyolviaszt és mindenkinek megpróbálnak beugatni, ha kritikát merészel megfogalmazni az imádott nyelvükkel kapcsolatban. Építő jellegű kritika nélkül sose fog beérni, úgyhogy pont a hypevonatos Rustafárik miatt fog megbukni ez a nyelv.

azokhoz képest akár soványkának is lehetne nevezni.

Inkább a többi libc-hez kell mérni, pl. MUSL, vagy uClibc vagy newlib.

Szerkesztve: 2024. 11. 20., sze – 20:50

Probalj meg a puts() helyett egy write()-t. Pl write(1,"xyz\n",4); Az a write() valojaban egy makro a __libc_write-ra, de ha static-cal forditod es ugy csinalsz egy `objdump -d`-t, akkor ott a <__libc_write>: sorra rakeresve  mar latni fogod azonnal a syscall-okat! Sot, meg az is viszonylag gyorsan latszik hogy a syscall-t koveto-megelozo feltetelek a tobbszalusag eseten fellepo esetek meg az errno-beallitasok kapcsan vannak, de ha azokkal minden klappol, akkor pont ugyanaz a rax=1-es syscall lesz mint a nyitoban fent a peldad:

0000000000430620 <__libc_write>:
  430620:       80 3d 31 4a 07 00 00    cmpb   $0x0,0x74a31(%rip)        # 4a5058 <__libc_single_threaded>
  430627:       74 17                   je     430640 <__libc_write+0x20>
  430629:       b8 01 00 00 00          mov    $0x1,%eax
  43062e:       0f 05                   syscall
  430630:       48 3d 00 f0 ff ff       cmp    $0xfffffffffffff000,%rax
  430636:       77 58                   ja     430690 <__libc_write+0x70>
  430638:       c3                      ret
  430639:       0f 1f 80 00 00 00 00    nopl   0x0(%rax)
[...]

Simán kicserélve nem lett jó.

 

pentike@5CG3464HPJ:~/sandbox/asm$ cat hello.c
#include <stdio.h>

int main(void) {
	write(1,"Hello World!\n",13);
	return 0;
}
pentike@5CG3464HPJ:~/sandbox/asm$ gcc -Wall hello.c -o helloc --static
hello.c: In function ‘main’:
hello.c:4:9: warning: implicit declaration of function ‘write’; did you mean ‘fwrite’? [-Wimplicit-function-declaration]
    4 |         write(1,"Hello World!\n",13);
      |         ^~~~~
      |         fwrite

A többiek majd javítanak ha szerintük rosszat mondok, de szerintem ott rontottad el, hogy túl magas szintű eljárásal akartál valamit bemutatni. Ha puts, vagy printf vagy hasonló, akkor az már a "nagy" C-függvénykönyvtár része (man, 3. fejezet) - ilyen feladatra egyértelműen sokkal jobb a rendszerhívásokhoz sokkal közelebbi példákat használó eszköz - ezért is használhatóbb a fenti write-os példa (elvben ugye a write az rendszerhívás -  system call - "rendes" rendszerben a man 2. fejezet.) A man-t amúgy se hátrány ilyen esetben lapozgatni, elvben legalábbis ildomos lenne ilyeneket leírni, ami itt is kiderült, hogy stdio-t vagy unistd-t kell-e inkludálni.

Ajánlom a https://godbolt.org/ oldalt. A bal oldali panelbe beírod a C kódot, jobb oldalon meg kb. valós időben látod az assembly-t, az egymásnak megfelelő részeket azonos színnel kiemelve. Választhatsz egy csomó féle nyelvet és fordítóprogramot.

graal vm-mel lehet binárisra fordítani Java programot. Indulás teljesítmény okokból szokták használni például. Hogy a generált ASM milyen, meg hogy hogy kell megnézni arról fogalmam sincsen sajnos. (Subscribe)