3

拆卸printf并没有提供太多信息:

(gdb) disas printf
Dump of assembler code for function printf:
0x00401b38 <printf+0>:  jmp    *0x405130
0x00401b3e <printf+6>:  nop
0x00401b3f <printf+7>:  nop
End of assembler dump.


(gdb) disas 0x405130
Dump of assembler code for function _imp__printf:
0x00405130 <_imp__printf+0>:    je     0x405184 <_imp__vfprintf+76>
0x00405132 <_imp__printf+2>:    add    %al,(%eax)

它是如何在幕后实现的?

为什么拆机没有用?

之前是什么*意思0x405130

4

6 回答 6

8

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

于 2010-04-01T11:03:08.930 回答
3

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

jmp *<addr>

意思是“跳转到存储在<addr>”中的地址。

它等效于以下 Intel 语法:

jmp [addr]

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

资源

于 2010-04-04T03:08:36.677 回答
2

几乎所有 C 编译器都提供其运行时库的源代码——而不仅仅是开源编译器。不幸的是,它们通常以相当难以遵循的形式编写,并且它们通常不附带设计原理文档。

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

以这本书的某些“二手”版本的价格来看,这是一种偷窃,应该放在任何认真的 C 程序员的书架上。

Plauger 有类似的针对 C++ 库的书籍,我认为它们具有相似的价值:

于 2010-04-01T17:39:20.150 回答
1

我想说在这里反汇编工作得很好,并且 printf 在这里使用 vfprintf 实现了“幕后”,这几乎是您所期望的。请注意,汇编程序通常比 C 更冗长,并且要花时间来了解您没有带注释的源代码的位置。编译器输出也不是自学汇编程序的好方法。

于 2010-04-01T11:45:39.520 回答
1

至于

0x405130之前的*是什么意思?

我不熟悉 gdb 的反汇编程序,但它看起来像是jmp *0x405130通过指针的间接跳转。与其反汇编 0x405130 的内容,不如将 4 字节的内存转储到那里。我敢打赌你会在那里找到另一个地址,如果你反汇编那个位置,你会找到printf()'s 的代码(反汇编的可读性可能是另一回事)。

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


在下面的评论中获得更多信息后进行编辑:

在使用 Intel 汇编语法时,稍微四处寻找表明这jmp *0x405130是指令的 GAS/AT&T 汇编语法。jmp [0x405130]

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

_imp_printf条目可能正在使用某种惰性绑定技术,其中第一次执行跳转到 0x405130,它会到达 0x00005274 地址,这会导致操作系统设置陷阱并修复动态链接。修复后,操作系统将使用 0x405130 中的正确链接地址重新开始执行。但这纯粹是我的猜测。我不知道您使用的系统是否会执行此类操作(实际上,我什至不知道您正在运行什么系统),但在技术上是可行的。如果发生这样的事情,直到第一次调用之后,您才会在 0x405130 中看到正确的地址printf()

我认为您需要单步printf()执行装配级别的调用,以了解实际情况。


使用 GDB 会话更新信息:

这是您遇到的问题 - 您正在查看系统加载 DLL 并修复与 DLL 的链接之前的进程。下面是一个简单的“hello world”程序的调试会话,该程序使用 MinGW 编译并使用 GDB 进行调试:

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 <http://gnu.org/licenses/gpl.html>
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:
<http://www.gnu.org/software/gdb/bugs/>...
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 <printf>
   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符号作为代码毫无意义......

(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现在指向的内容,它可能不太可读,但显然它现在是代码。这是printf()在 MSVCRT.DLL 中实现的:

(gdb) disas _imp__printf
Dump of assembler code for function printf:
   0x77bd27c2 <+0>:     push   $0x10
   0x77bd27c4 <+2>:     push   $0x77ba4770
   0x77bd27c9 <+7>:     call   0x77bc84c4 <strerror+554>
   0x77bd27ce <+12>:    mov    $0x77bf1cc8,%esi
   0x77bd27d3 <+17>:    push   %esi
   0x77bd27d4 <+18>:    push   $0x1
   0x77bd27d6 <+20>:    call   0x77bcca49 <msvcrt!_lock+4816>
   0x77bd27db <+25>:    pop    %ecx
   0x77bd27dc <+26>:    pop    %ecx
   0x77bd27dd <+27>:    andl   $0x0,-0x4(%ebp)
   0x77bd27e1 <+31>:    push   %esi
   0x77bd27e2 <+32>:    call   0x77bd400d <wscanf+3544>
   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 <wscanf+251>
   0x77bd27f7 <+53>:    mov    %eax,-0x20(%ebp)
   0x77bd27fa <+56>:    push   %esi
   0x77bd27fb <+57>:    pushl  -0x1c(%ebp)
   0x77bd27fe <+60>:    call   0x77bd4099 <wscanf+3684>
   0x77bd2803 <+65>:    add    $0x18,%esp
   0x77bd2806 <+68>:    orl    $0xffffffff,-0x4(%ebp)
   0x77bd280a <+72>:    call   0x77bd281d <printf+91>
   0x77bd280f <+77>:    mov    -0x20(%ebp),%eax
   0x77bd2812 <+80>:    call   0x77bc84ff <strerror+613>
   0x77bd2817 <+85>:    ret
   0x77bd2818 <+86>:    mov    $0x77bf1cc8,%esi
   0x77bd281d <+91>:    push   %esi
   0x77bd281e <+92>:    push   $0x1
   0x77bd2820 <+94>:    call   0x77bccab0 <msvcrt!_lock+4919>
   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也附带运行时源(但我可能错了)。

于 2010-04-02T07:33:24.903 回答
0

printf()很可能位于动态共享库中。动态链接器用导入函数的地址填充一个表;这就是为什么您必须进行间接调用的原因。

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

于 2010-04-01T11:26:58.750 回答