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élyra.

 

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.