cra*_*ace 5 linux assembly gcc x86-64 nasm
我有一个简单的调试器(使用ptrace:http://pastebin.com/D0um3bUi)来计算给定输入可执行程序执行的指令数.它使用ptrace单步执行模式来计算指令.
为此,当程序1)的可执行文件(来自gcc main.c的a.out)作为输入提供给我的测试调试器时,它会在执行指令时打印大约100k.当我使用-static
选项时,它会给出10681条指令.
现在在2)我创建一个汇编程序并使用NASM进行编译和链接,然后当这个可执行文件作为测试调试器输入时,它显示8个指令作为计数,哪个是apt.
程序1)中执行的指令数量很高,因为在运行时将程序与系统库链接起来了?使用-static并将计数减少1/10.如何确保指令计数仅是程序1)中主要功能的指令,以及程序2)为调试器报告的方式?
1)
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我使用gcc来创建可执行文件.
2)
; 64-bit "Hello World!" in Linux NASM
global _start ; global entry point export for ld
section .text
_start:
; sys_write(stdout, message, length)
mov rax, 1 ; sys_write
mov rdi, 1 ; stdout
mov rsi, message ; message address
mov rdx, length ; message string length
syscall
; sys_exit(return_code)
mov rax, 60 ; sys_exit
mov rdi, 0 ; return 0 (success)
syscall
section .data
message: db 'Hello, world!',0x0A ; message and newline
length: equ $-message ; NASM definition pseudo-
Run Code Online (Sandbox Code Playgroud)
nasm -f elf64 -o main.o -s main.asm
ld -o main main.o
彼得给出了一个非常好的答案,我将跟进一个非常有价值的反应并且可能会获得一些选票.当直接与LD链接或间接与GCC链接时,ELF可执行文件的默认入口点是标签_start
.
您的NASM代码使用全局标签,_start
因此当您的程序运行时,程序中的第一个代码将是指令_start
.使用GCC时,程序的典型入口点就是函数main
.你隐藏的是你的C程序也有一个_start
标签,但它是由C运行时启动对象提供的.
现在的问题是 - 有没有办法绕过C启动文件,以便可以避免启动代码?技术上是的,但这是危险的领域,可能产生不确定的行为.如果您有冒险精神,您实际上可以告诉GCC使用-e
命令行选项更改程序的入口点.而不是_start
我们可以使我们的入口点main
绕过C启动代码.由于我们绕过C启动代码,我们也可以省去C运行时启动代码中的-nostartfiles
选项链接.
您可以使用此命令行来编译C程序:
gcc test.c -e main -nostartfiles
Run Code Online (Sandbox Code Playgroud)
不幸的是,有一些必须在C代码中修复的gotchya .通常情况下使用时Ç运行时启动对象,环境初始化之后CALL就是制作main
.通常main
会返回返回C运行时代码的RET指令.此时,C运行时会正常退出程序.使用该选项时,RET无处可返回,因此可能会出现段错误.为了解决这个问题,我们可以调用C库函数来退出程序. -nostartfiles
_exit
#include <stdio.h>
int main()
{
printf("Hello, world!\n");
_exit(0); /* We exit application here, never reaching the return */
return 0;
}
Run Code Online (Sandbox Code Playgroud)
除非省略帧指针,否则GCC会发出一些额外的指令来设置堆栈帧并将其拆除,但开销很小.
上面的过程似乎不适用于标准glibc C库的静态构建(GCC中的-static
选项).这在Stackoverflow的答案中讨论过.动态版本有效,因为共享对象可以注册由动态加载程序调用以执行初始化的函数.静态构建时,这通常由C运行时完成,但我们已经跳过了初始化.因为GLIBC的功能就像失败一样.有一些符合标准的替换C库可以在没有C运行时初始化的情况下运行.其中一种产品是MUSL.printf
在Ubuntu 64位上,这些命令应该构建并安装64位版本的MUSL:
git clone git://git.musl-libc.org/musl
cd musl
./configure --prefix=/usr/local/musl/x86-64
make
sudo make install
Run Code Online (Sandbox Code Playgroud)
然后,您可以使用MUSL包装器GCC与合作MUSL的ç库,而不是默认GLIBC在大多数Linux发行库.参数就像GCC一样,所以你应该能够做到:
/usr/local/musl/x86-64/bin/musl-gcc -e main -static -nostartfiles test.c
Run Code Online (Sandbox Code Playgroud)
当./a.out
使用GLIBC生成运行时,它可能会发生段错误.在使用大多数C库函数之前,MUSL不需要初始化,因此即使使用GCC选项也应该可以工作.-static
一个与你的比较问题是,你调用SYS_WRITE直接系统调用在NASM,在ç你正在使用printf
.用户EOF正确评论说您可能希望通过调用C中的write
函数而不是使用它来使其更公平.它的开销要少得多.您可以修改您的代码:printf
write
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
char *str = "Hello, world\n";
write (STDOUT_FILENO, str, 13);
_exit(0);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这将比NASM的直接SYS_WRITE系统调用具有更多的开销,但远远低于printf
生成的开销.
我将发出警告,除了软件开发的一些边缘情况之外,代码审查中可能不会很好地利用这些代码和技巧.
程序1)中执行的指令数量很高,因为在运行时将程序与系统库链接起来了?
是的,动态链接加上CRT(C运行时)启动文件.
使用
-static
并减少了1/10的计数.
因此,只需离开CRT启动文件,这些文件在调用之前执行,然后执行main
.
如何确保指令计数仅为程序1)中主函数的指令
测量一个空main
,然后从未来的测量值中减去该数字.
除非您的指令计数器更智能,并查看可执行文件中的符号以查找它正在跟踪的进程,否则它将无法分辨哪些代码来自哪里.
以及程序2)如何报告调试器.
这是因为有是在该程序中没有其他的代码.并不是因为你以某种方式帮助调试器忽略了一些指令,而是你创建了一个没有任何指令的程序,你没有自己放在那里.
如果你想看看有什么实际当你运行GCC的输出情况,gdb a.out
,b _start
,r
,和单步执行.一旦深入了解调用树,你就可能了.想要用来fin
完成当前函数的执行,因为你不想单步执行100万条指令,甚至10k.
归档时间: |
|
查看次数: |
1198 次 |
最近记录: |