*地址(在printf中找到)在汇编中意味着什么?

反汇编printf没有提供太多信息:

 (gdb) disas printf Dump of assembler code for function printf: 0x00401b38 : jmp *0x405130 0x00401b3e : nop 0x00401b3f : nop End of assembler dump. (gdb) disas 0x405130 Dump of assembler code for function _imp__printf: 0x00405130 : je 0x405184  0x00405132 : add %al,(%eax) 

它是如何在引擎盖下实施的?

为什么拆卸无济于事?

0x405130之前*含义是什么?

至于

在0x405130之前*的含义是什么?

我不熟悉gdb的反汇编程序,但看起来jmp *0x405130是通过指针的间接跳转。 而不是拆解0x405130处的内容,你应该在那里转储4个字节的内存。 我愿意打赌你会在那里找到另一个地址,如果你拆卸那个位置,你会发现printf()的代码(反汇编的可读性是另一个故事)。

换句话说, _imp__printf是指向printf()的指针,而不是printf()本身。


根据以下评论中的更多信息进行编辑:

在使用英特尔汇编语法时, jmp *0x405130jmp [0x405130]指令的GAS / AT&T汇编语法。

让你感到好奇的是,你说gdb命令x/xw 0x405130显示该地址包含0x00005274 (这似乎与你在反汇编0x405130时所得到的相匹配)。 但是,这意味着jmp [0x405130]会尝试跳转到地址0x00005274 ,这似乎不正确(当你试图反汇编该地址时,gdb 0x00005274说。

_imp_printf条目可能使用某种惰性绑定技术,其中第一次执行跳过0x405130,它命中0x00005274地址,这导致操作系统记录陷阱并修复动态链接。 修复后,操作系统将使用0x405130中的正确链接地址重新启动执行。 但这对我来说纯粹是猜测。 我不知道你使用的系统是否做了这样的事情(事实上,我甚至不知道你在运行什么系统),但这在技术上是可行的。 如果发生类似这样的事情,则在第一次调用printf()之后才会在0x405130中看到正确的地址。

我认为你需要在程序集层面单步调用printf()来查看真正发生的事情。


使用GDB会话更新了信息:

这是您遇到的问题 – 您在系统加载DLL之前查看了该过程并修复了与DLL的链接。 这是使用GDB调试的MinGW编译的简单“hello world”程序的调试会话:

 C:\temp>\mingw\bin\gdb test.exe GNU gdb (GDB) 7.1 Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later  This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "mingw32". For bug reporting instructions, please see: ... Reading symbols from C:\temp/test.exe...done. (gdb) disas main Dump of assembler code for function main: 0x004012f0 <+0>: push %ebp 0x004012f1 <+1>: mov %esp,%ebp 0x004012f3 <+3>: sub $0x8,%esp 0x004012f6 <+6>: and $0xfffffff0,%esp 0x004012f9 <+9>: mov $0x0,%eax 0x004012fe <+14>: add $0xf,%eax 0x00401301 <+17>: add $0xf,%eax 0x00401304 <+20>: shr $0x4,%eax 0x00401307 <+23>: shl $0x4,%eax 0x0040130a <+26>: mov %eax,-0x4(%ebp) 0x0040130d <+29>: mov -0x4(%ebp),%eax 0x00401310 <+32>: call 0x401850 <_alloca> 0x00401315 <+37>: call 0x4013d0 <__main> 0x0040131a <+42>: movl $0x403000,(%esp) 0x00401321 <+49>: call 0x4018b0  0x00401326 <+54>: mov $0x0,%eax 0x0040132b <+59>: leave 0x0040132c <+60>: ret End of assembler dump. 

请注意,反汇编printf()会导致类似的间接跳转:

 (gdb) disas printf Dump of assembler code for function printf: 0x004018b0 <+0>: jmp *0x4050f8 ; <<-- indirect jump 0x004018b6 <+6>: nop 0x004018b7 <+7>: nop End of assembler dump. 

并且_imp__printf symbiol作为代码毫无意义……

 (gdb) disas 0x4050f8 Dump of assembler code for function _imp__printf: 0x004050f8 <+0>: clc ; <<-- how can this be printf()? 0x004050f9 <+1>: push %ecx 0x004050fa <+2>: add %al,(%eax) End of assembler dump. 

或作为指针……

 (gdb) x/xw 0x4050f8 0x4050f8 <_imp__printf>: 0x000051f8 ; <<-- 0x000051f8 is an invalid pointer 

现在,让我们在main()处设置一个断点,然后运行它:

 (gdb) break main Breakpoint 1 at 0x40131a: file c:/temp/test.c, line 5. (gdb) run Starting program: C:\temp/test.exe [New Thread 11204.0x2bc8] Error while mapping shared library sections: C:\WINDOWS\SysWOW64\ntdll32.dll: No such file or directory. Breakpoint 1, main () at c:/temp/test.c:5 5 printf( "hello world\n"); 

printf()看起来一样:

 (gdb) disas printf Dump of assembler code for function printf: 0x004018b0 <+0>: jmp *0x4050f8 0x004018b6 <+6>: nop 0x004018b7 <+7>: nop End of assembler dump. 

_imp__printf看起来不同 - 动态链接现已修复:

 (gdb) x/xw 0x4050f8 0x4050f8 <_imp__printf>: 0x77bd27c2 

如果我们反汇编_imp__printf现在指向的内容,它可能不是非常易读,但现在显然是代码。 这是在MSVCRT.DLL中实现的printf()

 (gdb) disas _imp__printf Dump of assembler code for function printf: 0x77bd27c2 <+0>: push $0x10 0x77bd27c4 <+2>: push $0x77ba4770 0x77bd27c9 <+7>: call 0x77bc84c4  0x77bd27ce <+12>: mov $0x77bf1cc8,%esi 0x77bd27d3 <+17>: push %esi 0x77bd27d4 <+18>: push $0x1 0x77bd27d6 <+20>: call 0x77bcca49  0x77bd27db <+25>: pop %ecx 0x77bd27dc <+26>: pop %ecx 0x77bd27dd <+27>: andl $0x0,-0x4(%ebp) 0x77bd27e1 <+31>: push %esi 0x77bd27e2 <+32>: call 0x77bd400d  0x77bd27e7 <+37>: mov %eax,-0x1c(%ebp) 0x77bd27ea <+40>: lea 0xc(%ebp),%eax 0x77bd27ed <+43>: push %eax 0x77bd27ee <+44>: pushl 0x8(%ebp) 0x77bd27f1 <+47>: push %esi 0x77bd27f2 <+48>: call 0x77bd3330  0x77bd27f7 <+53>: mov %eax,-0x20(%ebp) 0x77bd27fa <+56>: push %esi 0x77bd27fb <+57>: pushl -0x1c(%ebp) 0x77bd27fe <+60>: call 0x77bd4099  0x77bd2803 <+65>: add $0x18,%esp 0x77bd2806 <+68>: orl $0xffffffff,-0x4(%ebp) 0x77bd280a <+72>: call 0x77bd281d  0x77bd280f <+77>: mov -0x20(%ebp),%eax 0x77bd2812 <+80>: call 0x77bc84ff  0x77bd2817 <+85>: ret 0x77bd2818 <+86>: mov $0x77bf1cc8,%esi 0x77bd281d <+91>: push %esi 0x77bd281e <+92>: push $0x1 0x77bd2820 <+94>: call 0x77bccab0  0x77bd2825 <+99>: pop %ecx 0x77bd2826 <+100>: pop %ecx 0x77bd2827 <+101>: ret 0x77bd2828 <+102>: int3 0x77bd2829 <+103>: int3 0x77bd282a <+104>: int3 0x77bd282b <+105>: int3 0x77bd282c <+106>: int3 End of assembler dump. 

它可能比你希望的更难阅读,因为我不确定它是否有适当的符号(或者GDB是否可以正确读取这些符号)。

但是, 正如我在另一个答案中提到的 ,您通常可以使用编译器获取C运行时例程的源代码,无论是否为开源代码。 MinGW没有附带MSVDRT.DLL的源代码,因为这是一个Windows的东西,但你可以在Visual Studio发行版中获得它的源代码(或者非常接近它的东西) - 我认为即使是免费的VC ++ Express随附运行时源(但我可能错了)。

这是一个特殊的实现, http://ftp.fr.openbsd.org/pub/OpenBSD/src/lib/libc/stdio/printf.c和http://ftp.fr.openbsd.org/pub/OpenBSD/src /lib/libc/stdio/vfprintf.c

*是用于间接内存引用的AT&T汇编语法。 即

 jmp * 

表示“跳转到存储在中的 ”。

它等同于以下Intel语法:

 jmp [addr] 

使用寄存器或存储器操作数的分支寻址必须以’*’为前缀

资源

实际上,所有C编译器都为源提供了运行时库 – 而不仅仅是开源编译器。 不幸的是,它们通常以难以遵循的forms编写,并且通常不带有设计原理文档。

因此,处理该问题的一个非常好的资源是PJ Plauger的“标准C库” ,它不仅提供了库实现的源,而且还详细说明了它的设计方式以及这种库可能具有的特殊情况。考虑。

按照本书的某些“二手”版本的价格,这是一个窃取,应该在任何严肃的C程序员的书架上。

Plauger有类似的书籍,我认为C ++库有类似的价值:

  • 标准C ++库草案
  • C ++标准模板库

我说拆解工作在这里很好,并且printf是在引擎盖下实现的,使用vfprintf,这几乎是你所期望的。 请注意,汇编程序通常比C更冗长,并且需要花费时间来了解没有注释源的位置。 编译器输出也不是教你自己汇编程序的好方法。

printf()很可能位于动态共享库中。 动态链接器使用导入函数的地址填充表; 这就是你必须进行间接呼叫的原因。

我真的不记得这是如何运作的; 优化可能会使流程复杂化。 但是你明白了。