在简单程序上使用 ftrace,内联汇编 __asm__("leave") 导致段错误
Using ftrace on simple program, Inline Assembly __asm__("leave") resulting in seg fault
我正在通读这本关于学习 Linux 二进制分析的书。在书中,作者介绍了 ftrace,他在 github 上安装了它,并演示了如何使用它。他提供了一小段代码来测试 ftrace。
当运行在其上使用 ftrace 时,没有任何反应。如果我 运行 可执行文件本身,我只会遇到段错误。
我是这样编译的:gcc -nostdlib test.c -o test
这是我的代码:
int foo(void) {
}
_start()
{
foo();
__asm__("leave");
}
预期结果表明 ftrace 通过执行跟踪函数调用。
这是我要讲的内容的图片:
这是我正在使用的 ftrace:
https://github.com/elfmaster/ftrace
我想问题是,我是否完全遗漏了什么,做错了什么,文本是否过时或者正确的方法是什么?如果这是一个愚蠢的问题,我深表歉意,我只是离开了正文。我还在 VM 上使用 32 位发行版尝试了此操作,但没有任何改变,但只是尝试了一下,因为作者的一些示例是在 32 位上。谢谢。
注意: 当我 运行 他的 ftrace 使用不会导致段错误的程序时,我得到
pid_read() failed: Input/output error <0x1>
在_start
的末尾调用_exit(0);
或exit_group(0)
。 (Link 使用 gcc -static -nostartfiles
而不是 -nostdlib
,因此您可以调用 libc 系统调用包装函数;即使 glibc init 函数尚未被 运行 ).
或使用内联汇编手动创建 exit_group(0)
system-call。在 x86-64 上 Linux:
asm("mov 1, %eax; xor %edi,%edi; syscall");
另请参阅 How Get arguments value using inline assembly in C without Glibc? 以了解更多有关将 hacky x86-64 _start
编写到 运行 您自己的 C 函数的更多信息,作为您流程中的第一件事。 (但大部分答案都是关于破解调用约定以访问 argc / argv,这是令人讨厌的,我不推荐它。)Matteo 对这个问题的回答有一个用 asm 编写的最小 _start
调用正常的 C main
函数。
由于 2 个原因,本书的代码完全错误。 (我不知道它是如何在 i386 或 x86-64 上工作的。对我来说似乎很奇怪。你确定它不应该崩溃,但你看看它在崩溃之前做了什么?)
_start
不是 Linux 中的函数;你(或 compiler-generated 代码)不能从它 ret
。您需要进行 _exit
系统调用。栈上没有return地址1.
如果函数有其 return 地址,ELF 入口点 _start
有 argc
,如 ABI 文档中所指定。 (x86-64 System V 或 i386 System V 取决于您构建的是 64 位还是 gcc -m32
32 位可执行文件。)
插入leave
(mov %ebp, %esp
/pop %ebp
或等效的RBP/RSP)进入compiler-generated代码在这里没有意义。它有点像额外的 pop
,但会破坏编译器的 EBP
/RBP
,所以如果它碰巧选择 leave
而不是 pop %rbp
作为它自己的序言,那么编译器生成的代码将出错。 (进入 _start
的 RBP 在 statically-linked 可执行文件中为 0。或者在跳转到 PIE 可执行文件中的 _start
之前保留 RBP 中剩余的任何动态 linker。)
但最终,GCC 会将 _start
编译为普通函数,因此最终 运行 会生成一条 ret
指令。任何地方都没有有效/有用的 return 地址,因此 ret
根本无法工作。
如果你编译时没有优化(默认),gcc 将默认为 -fno-omit-frame-pointer
,因此它的函数序言将设置 EBP 或 RBP 作为帧指针,使 leave
本身成为可能不要错。如果您使用优化进行编译(-O1
和更高版本启用 -fomit-frame-pointer
),gcc 不会与 RBP 混淆,并且当您 运行 leave
时它将为零,因此直接导致段错误。 (因为它执行 RSP=RBP 然后使用新的 RSP 作为 pop %rbp
的堆栈指针。)
无论如何,如果它没有出错,作为正常函数尾声的一部分,在 compiler-generated pop %rbp
之前,堆栈指针将再次指向 argc
。 因此 compiler-generated ret
将尝试 return 到 argv[0]
。由于堆栈默认为 non-executable,这将导致段错误。(它指向 ASCII 字符,可能无法解码为有用的 x86-64 机器码。)
您可以通过 single-stepping GDB 的 asm 自己发现这一点。 (layout reg
并使用 stepi
又名 si
)。
一般来说,你在编译器背后乱搞堆栈指针和其他寄存器通常只会让事情崩溃。如果在堆栈上有一个 return 地址,pop %rcx
将比 leave
.
更有意义
脚注 1:
在您的进程的地址 space 中甚至没有任何机器代码,一个有用的 return 地址 可以 指向创建这样一个系统调用,除非您将一些机器代码作为 arg 或环境变量注入。
你 linked 与 -nostdlib
所以没有 libc linked。如果您动态地执行了 link libc,但仍然编写了自己的 _start
(例如,使用 gcc -nostartfiles
而不是完整的 -nostdlib
),ASLR 将意味着 libc _exit
函数在某个 runtime-variable 地址。
如果您静态 linked libc (gcc -nostartfiles -static
),_exit()
的代码将不会被复制到您的可执行文件中,除非您实际引用它,而这段代码不会吨。但是您仍然需要以某种方式调用它;没有 return 地址指向它。
我正在通读这本关于学习 Linux 二进制分析的书。在书中,作者介绍了 ftrace,他在 github 上安装了它,并演示了如何使用它。他提供了一小段代码来测试 ftrace。
当运行在其上使用 ftrace 时,没有任何反应。如果我 运行 可执行文件本身,我只会遇到段错误。 我是这样编译的:gcc -nostdlib test.c -o test
这是我的代码:
int foo(void) {
}
_start()
{
foo();
__asm__("leave");
}
预期结果表明 ftrace 通过执行跟踪函数调用。
这是我要讲的内容的图片:
这是我正在使用的 ftrace:
https://github.com/elfmaster/ftrace
我想问题是,我是否完全遗漏了什么,做错了什么,文本是否过时或者正确的方法是什么?如果这是一个愚蠢的问题,我深表歉意,我只是离开了正文。我还在 VM 上使用 32 位发行版尝试了此操作,但没有任何改变,但只是尝试了一下,因为作者的一些示例是在 32 位上。谢谢。
注意: 当我 运行 他的 ftrace 使用不会导致段错误的程序时,我得到
pid_read() failed: Input/output error <0x1>
在_start
的末尾调用_exit(0);
或exit_group(0)
。 (Link 使用 gcc -static -nostartfiles
而不是 -nostdlib
,因此您可以调用 libc 系统调用包装函数;即使 glibc init 函数尚未被 运行
或使用内联汇编手动创建 exit_group(0)
system-call。在 x86-64 上 Linux:
asm("mov 1, %eax; xor %edi,%edi; syscall");
另请参阅 How Get arguments value using inline assembly in C without Glibc? 以了解更多有关将 hacky x86-64 _start
编写到 运行 您自己的 C 函数的更多信息,作为您流程中的第一件事。 (但大部分答案都是关于破解调用约定以访问 argc / argv,这是令人讨厌的,我不推荐它。)Matteo 对这个问题的回答有一个用 asm 编写的最小 _start
调用正常的 C main
函数。
由于 2 个原因,本书的代码完全错误。 (我不知道它是如何在 i386 或 x86-64 上工作的。对我来说似乎很奇怪。你确定它不应该崩溃,但你看看它在崩溃之前做了什么?)
_start
不是 Linux 中的函数;你(或 compiler-generated 代码)不能从它ret
。您需要进行_exit
系统调用。栈上没有return地址1.如果函数有其 return 地址,ELF 入口点
_start
有argc
,如 ABI 文档中所指定。 (x86-64 System V 或 i386 System V 取决于您构建的是 64 位还是gcc -m32
32 位可执行文件。)插入
leave
(mov %ebp, %esp
/pop %ebp
或等效的RBP/RSP)进入compiler-generated代码在这里没有意义。它有点像额外的pop
,但会破坏编译器的EBP
/RBP
,所以如果它碰巧选择leave
而不是pop %rbp
作为它自己的序言,那么编译器生成的代码将出错。 (进入_start
的 RBP 在 statically-linked 可执行文件中为 0。或者在跳转到 PIE 可执行文件中的_start
之前保留 RBP 中剩余的任何动态 linker。)但最终,GCC 会将
_start
编译为普通函数,因此最终 运行 会生成一条ret
指令。任何地方都没有有效/有用的 return 地址,因此ret
根本无法工作。如果你编译时没有优化(默认),gcc 将默认为
-fno-omit-frame-pointer
,因此它的函数序言将设置 EBP 或 RBP 作为帧指针,使leave
本身成为可能不要错。如果您使用优化进行编译(-O1
和更高版本启用-fomit-frame-pointer
),gcc 不会与 RBP 混淆,并且当您 运行leave
时它将为零,因此直接导致段错误。 (因为它执行 RSP=RBP 然后使用新的 RSP 作为pop %rbp
的堆栈指针。)
无论如何,如果它没有出错,作为正常函数尾声的一部分,在 compiler-generated pop %rbp
之前,堆栈指针将再次指向 argc
。 因此 compiler-generated ret
将尝试 return 到 argv[0]
。由于堆栈默认为 non-executable,这将导致段错误。(它指向 ASCII 字符,可能无法解码为有用的 x86-64 机器码。)
您可以通过 single-stepping GDB 的 asm 自己发现这一点。 (layout reg
并使用 stepi
又名 si
)。
一般来说,你在编译器背后乱搞堆栈指针和其他寄存器通常只会让事情崩溃。如果在堆栈上有一个 return 地址,pop %rcx
将比 leave
.
脚注 1:
在您的进程的地址 space 中甚至没有任何机器代码,一个有用的 return 地址 可以 指向创建这样一个系统调用,除非您将一些机器代码作为 arg 或环境变量注入。
你 linked 与 -nostdlib
所以没有 libc linked。如果您动态地执行了 link libc,但仍然编写了自己的 _start
(例如,使用 gcc -nostartfiles
而不是完整的 -nostdlib
),ASLR 将意味着 libc _exit
函数在某个 runtime-variable 地址。
如果您静态 linked libc (gcc -nostartfiles -static
),_exit()
的代码将不会被复制到您的可执行文件中,除非您实际引用它,而这段代码不会吨。但是您仍然需要以某种方式调用它;没有 return 地址指向它。