对于包括设置在内的小型 C 程序,合理的最小汇编指令数是多少?

2023-11-27

我正在尝试生成尽可能小的 C 程序,以查看通过运行它执行了多少条指令。我禁用了库的使用并禁用了 vdso。然而,根据 perf stat,我的 C 程序(gdb 说是 7 条汇编指令)最终执行了 17k 条指令。

这是用于设置程序的正常指令量吗?根据gdb,来自ld-linux-x86-64.so.2的代码被映射到程序地址空间。鉴于我禁用了 vdso 并且不包含任何库,运行该程序是否需要此文件?这可能是 17k 指令的原因吗?

我的C程序foo5.c

int main(){
    char* str = "Hello World";
    return 0;
}

我如何编译:

gcc -nostdlib -nodefaultlibs stubstart.S -o foo5 foo5.c

存根启动器

.globl _start
_start:call main;
    movl $1, %eax; 
    xorl %ebx, %ebx; 
    int $0x80

perf统计输出:

Performance counter stats for './foo5':

              0.60 msec task-clock:u              #    0.015 CPUs utilized          
                 0      context-switches:u        #    0.000 K/sec                  
                 0      cpu-migrations:u          #    0.000 K/sec                  
                11      page-faults:u             #    0.018 M/sec                  
            46,646      cycles:u                  #    0.077 GHz                    
            17,224      instructions:u            #    0.37  insn per cycle         
             5,145      branches:u                #    8.513 M/sec                  
               435      branch-misses:u           #    8.45% of all branches  

gdb节目布局:

`/home/foo5', file type elf64-x86-64.
    Entry point: 0x5555555542b1
    0x0000555555554238 - 0x0000555555554254 is .interp
    0x0000555555554254 - 0x0000555555554278 is .note.gnu.build-id
    0x0000555555554278 - 0x0000555555554294 is .gnu.hash
    0x0000555555554298 - 0x00005555555542b0 is .dynsym
    0x00005555555542b0 - 0x00005555555542b1 is .dynstr
    0x00005555555542b1 - 0x00005555555542d5 is .text
    0x00005555555542d5 - 0x00005555555542e1 is .rodata
    0x00005555555542e4 - 0x00005555555542f8 is .eh_frame_hdr
    0x00005555555542f8 - 0x0000555555554330 is .eh_frame
    0x0000555555754f20 - 0x0000555555755000 is .dynamic
    0x00007ffff7dd51c8 - 0x00007ffff7dd51ec is .note.gnu.build-id in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd51f0 - 0x00007ffff7dd52c4 is .hash in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd52c8 - 0x00007ffff7dd53c0 is .gnu.hash in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd53c0 - 0x00007ffff7dd56f0 is .dynsym in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd56f0 - 0x00007ffff7dd5914 is .dynstr in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd5914 - 0x00007ffff7dd5958 is .gnu.version in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd5958 - 0x00007ffff7dd59fc is .gnu.version_d in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd5a00 - 0x00007ffff7dd5dd8 is .rela.dyn in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd5dd8 - 0x00007ffff7dd5e80 is .rela.plt in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd5e80 - 0x00007ffff7dd5f00 is .plt in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd5f00 - 0x00007ffff7dd5f08 is .plt.got in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7dd5f10 - 0x00007ffff7df4b20 is .text in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7df4b20 - 0x00007ffff7df9140 is .rodata in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7df9140 - 0x00007ffff7df9141 is .stapsdt.base in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7df9144 - 0x00007ffff7df97b0 is .eh_frame_hdr in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7df97b0 - 0x00007ffff7dfbc24 is .eh_frame in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7ffc680 - 0x00007ffff7ffce64 is .data.rel.ro in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7ffce68 - 0x00007ffff7ffcfd8 is .dynamic in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7ffcfd8 - 0x00007ffff7ffcfe8 is .got in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7ffd000 - 0x00007ffff7ffd050 is .got.plt in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7ffd060 - 0x00007ffff7ffdfd8 is .data in /lib64/ld-linux-x86-64.so.2
    0x00007ffff7ffdfe0 - 0x00007ffff7ffe170 is .bss in /lib64/ld-linux-x86-64.so.2

UPDATE:

最后,jester 关于通过向 gcc 添加 -no-pie 标志来创建标准可执行文件而不是 PIE 来删除 ld.so 的评论将 perf 指令统计数据减少到 12。然后 old_timer 的 -O2 建议进一步将其减少到 7!谢谢大家。

更新2: 使用 -static 的选定答案还将指令数从 17k 减少到 12。非常好的答案。

Also 本文评论者链接的内容相关且有趣。


TL:DR: -static不是默认值,请使用它来制作仅运行您的 ELF 可执行文件_start.

-no-pie -nostdlib还将生成静态可执行文件,因为它不是 PIE 并且没有要链接的动态库。

还有这样的事情-static-pie内核会将您的可执行文件加载到随机基地址,但是not首先运行 ld.so (我认为),但这不是你得到的-static.


只是要明确的是,我们正在谈论的是dynamic指令数(实际上有多少executed在用户空间中,perf stat -e instructions:u),而不是静态计数(有多少作为可执行文件的一部分位于磁盘/内存中)。静态计数仅对循环内的指令进行一次计数,并且仍然对从未执行的指令进行计数。

或者至少我是这么回答的。这使得其他部分中的元数据以及不执行的代码变得无关紧要。

根据gdb,来自ld-linux-x86-64.so.2的代码被映射到程序地址空间。鉴于我禁用了 vdso 并且不包含任何库,运行该程序是否需要此文件?

您仍然构建了位置无关的可执行文件 (PIE)。这是一个带有入口点的 ELF 共享对象,因此它仍然是动态链接的。所以ld.so ELF解释器就在它上面运行。它没有什么可做的,因为您实际上没有使用任何共享库,但 17k 用户空间指令听起来是正确的。我在我的 Arch Linux 系统 (glibc 2.31) 上收到了 32606 或 7 个关于你的程序的指令。

ld.so作为二进制文件的“解释器”启动,其方式类似于/bin/sh开始解释以以下内容开头的可执行文本文件#!/bin/sh。 (虽然Linux的ELF程序加载器仍然根据可执行文件的程序头完成一些将程序段映射到内存的工作,所以ld.so不必通过系统调用手动完成这些工作。)

你可以通过运行来看到这一点gdb ./foo5并使用starti代替run在第一个用户空间指令之前停止。你会发现你在ld.so's _start.

Reading symbols from ./foo5...
(No debugging symbols found in ./foo5)
Cannot access memory at address 0x1024   ### note this isn't a real address,
                     ### just an offset relative to the base address / start of the file.
                     ### That's another clue this is a PIE
(gdb) starti

Program stopped.
0x00007ffff7fd3100 in _start () from /lib64/ld-linux-x86-64.so.2

你也可以运行strace ./foo5查看它所做的系统调用,表明发生了很多事情:

$ strace ./foo5
execve("./foo5", ["./foo5"], 0x7ffc12394d90 /* 50 vars */) = 0
brk(NULL)                               = 0x55741b4b7000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffca69312b0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f1d4fc4b000
arch_prctl(ARCH_SET_FS, 0x7f1d4fc4ba80) = 0
mprotect(0x557419622000, 4096, PROT_READ) = 0
strace: [ Process PID=303809 runs in 32 bit mode. ]
exit(0)                                 = ?

(注意“以 32 位模式运行”;它没有,但 strace 检测到您使用了 32 位int $0x80ABI代替正常的syscallld.so 使用的 ABI。)


Use -static

-nostdlib用于暗示-static,在 GCC 中默认配置为不生成 PIE。但出于安全原因,现代发行版确实会配置 GCC 来制作 PIE。看x86-64 Linux 中不再允许使用 32 位绝对地址?

$ file foo5
foo5: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1ac0a9af247fefebde100695805e5b73f06e891c, not stripped

构建后-static, OTOH:

$ file foo5
foo5: ELF 64-bit LSB executable ...
$ perf stat --all-user ./foo5

 Performance counter stats for './foo5':

              0.03 msec task-clock                #    0.151 CPUs utilized          
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 1      page-faults               #    0.030 M/sec                  
             1,930      cycles                    #    0.058 GHz                    
                12      instructions              #    0.01  insn per cycle         
                 4      branches                  #    0.121 M/sec                  
                 0      branch-misses             #    0.00% of all branches        

       0.000219151 seconds time elapsed

       0.000284000 seconds user
       0.000000000 seconds sys

(奇怪的是 perf 不打印:u对于您使用时的事件--all-user。我的系统有/proc/sys/kernel/perf_event_paranoid= 0 所以如果我不使用它,它也会计算内核内执行的指令。运行与运行之间的差异很大,但此静态可执行文件的总数约为 60k。)

我只计算了 11 条执行的用户空间指令,但显然我的 i7-6700k 在该事件中计算了 12 条。 (硬件支持对任何事件计数器屏蔽用户、内核或两者。这就是 perf 使用的功能。)

GDB 也确认了成功:

Reading symbols from ./foo5...
(No debugging symbols found in ./foo5)
Cannot access memory at address 0x401024
(gdb) starti
Starting program: /tmp/foo5

Program stopped.
0x0000000000401000 in _start ()
(gdb) 

以及反汇编窗口layout reg shows:

│  >0x401000 <_start>       call   0x40100e <main>
│   0x401005 <_start+5>     mov    eax,0x1
│   0x40100a <_start+10>    xor    ebx,ebx
│   0x40100c <_start+12>    int    0x80
│   0x40100e <main>         push   rbp
│   0x40100f <main+1>       mov    rbp,rsp
│   0x401012 <main+4>       lea    rax,[rip+0xfe7]        # 0x402000
│   0x401019 <main+11>      mov    QWORD PTR [rbp-0x8],rax
│   0x40101d <main+15>      mov    eax,0x0
│   0x401022 <main+20>      pop    rbp
│   0x401023 <main+21>      ret

你可以编译-O2来优化您的main减少到只有一个xor eax,eax / ret,或者根本不调用它,因此只需执行 3 个用户空间指令。

或者在仍然使用 C 的同时优化用户空间指令计数,请参阅@mosvy 的回答关于写作_start在 C 中,以及一个内联汇编_exit(2)可以内联到其中。)

请注意,您的 _start 无法将 argc 和 argv 传递给 main,尽管它在函数调用之前确实具有正确 16 字节对齐的 RSP。 (因为 x86-64 SysV ABI 保证进程条目在堆栈对齐的情况下发生)。您可以使用 mov load 和 LEA 来做到这一点。请注意,由于您没有初始化 libc,因此即使静态链接 libc,您也无法调用它的函数。

See 如何在不使用 Glibc 的情况下使用 C 中的内联汇编获取参数值?对于一些黑客。 (基本上是独立的asm_start写在一个asm()全局范围内的声明,或者我的答案是对调用约定的彻底修改。)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

对于包括设置在内的小型 C 程序,合理的最小汇编指令数是多少? 的相关文章

随机推荐

  • Maven远程资源插件问题

    我知道关于这个主题还有很多其他问题 我已经搜索并阅读了所有这些问题 但到目前为止还没有帮助 共享资源的 pom 看起来像这样
  • 如何通过第一次出现的分隔符来分割字符串?

    例如 我有一个文件 其中的行如下所示 KEY1 value1 KEY2 value2 有时字符串的值部分还包含 人物 KEY1 value1 thing1 KEY2 value2 thing2 在 Python 中 如果我只想要 KEY v
  • ASP.Net 下载文件到客户端浏览器

    我正在编写一个简单的测试页面 通过单击按钮从浏览器下载文本文件 我遇到了一个我以前从未见过的非常奇怪的错误 有什么想法吗 错误发生在Response End 并且该文件永远不会到达客户端浏览器 Code string filePath C
  • invalid_client 用于使用苹果登录

    我试图实现的目标 iOS 客户端向后端发送 JWT 令牌 后端 Java 调用https appleid apple com auth token验证令牌 到目前为止我所拥有的 拨打 Apple 验证电话 restTemplate new
  • 如何授权一组控制器而不在每个控制器上添加注释?

    我有一组控制器 每个控制器用于每种授权类型 例如 A 类授权将具有一组控制器 每个控制器都需要 A 类授权 有没有办法放置一个 Authorize Role Class A 某处的属性将适用于每个控制器 而不必用相同的属性装饰每个控制器 您
  • 为什么/什么时候适合重写 ToString?

    我正在学习 C 我想知道重写的意义和好处是什么ToString可能是这样 如下例所示 是否可以通过某种更简单的方式来完成此操作 使用通用方法而不需要覆盖 public string GetToStringItemsHeadings get
  • 如何将 PHP 应用程序限制在它们自己的目录和它们自己的 php.ini 中?

    我在 Mac 上运行多个 PHP 应用程序 运行 OS X 10 5 6 Apache 2 PHP 5 我为每个项目设置了子域 每个子域的主机文件条目以及 Apache 配置中的虚拟目录块 所以 project1 localhost 转到
  • SMTP 服务器需要安全连接或客户端未经过身份验证

    SMTP 服务器需要安全连接 或者客户端未经过身份验证 服务器响应为 5 7 0 必须首先发出 STARTTLS 命令 k12sm3795394wby 16 描述 当前Web请求执行期间发生未处理的异常 请查看堆栈跟踪以获取有关错误及其在代
  • 强制 64 位 jvm 到 32 位 jvm?

    有人可以给我关于强制 64 位 jvm 作为 32 位 jvm 运行的想法吗 我需要编写一个用于蓝牙连接的 jse 桌面应用程序 为此 我需要实现 Bluecove jar 它只有 32 位文件 所以我在 eclipse pulsar 的
  • 不支持关键字:“版本”

    我有一个在 VS2010 中作为 WinForms 项目编写的项目 我不是在 VS2012 中将其作为 WPF 项目编写 我有一个引用的DLL DailyReport 里面DailyReport是一个方法叫做GetUniqueDates 它
  • 转换使用反射创建的泛型类型实例

    我正在使用反射创建泛型类型的实例 public interface IModelBuilder
  • 如何找到空的 git 提交?

    我可以使用什么命令来查找 git 存储库中的空提交 即将被删除的提交git filter branch prune empty 您需要排除无父提交和合并提交 然后查看哪些提交与其父提交具有相同的树 for sha in git rev li
  • 如果线程启动 Executor,则无法从 Future 和 SwingWorker 获取 ArrayIndexOutOfBoundsException

    我通过使用 Executor 对 SwingWorker 进行多线程处理 并且错误地从 Vector 中识别出错误的元素 看起来像此代码相当忽略了 Vector 中不存在的元素 我的问题 gt 如何 可能以某种方式捕获此异常 简单输出 ru
  • 为什么仅采用 AVX 的处理器在许多 SIMD 算法方面的性能优于 AVX2 处理器?

    我一直在研究 C 和 C 中 SIMD 算法的优势 发现在许多情况下 在 AVX 处理器上使用 128 位寄存器比在具有 AVX2 的处理器上使用 256 位寄存器提供更好的改进 但我不这么认为 不明白为什么 我所说的改进是指在同一台机器上
  • PHP - foreach 因空合并运算符而丢失引用

    问题一 我觉得 在以下情况下将不执行任何操作 a 1 2 foreach a as v v var dump a But why array 2 0 gt int 1 1 gt int 2 问题2 这更奇怪了 foreach a 1 2 a
  • 如何将视频(从 getUserMedia)发送到 Node.js 服务器?

    我正在寻找构建一个聊天 直播应用程序 视频 文本聊天 我目前还没有确定一种方法 但我正在推进一种方法 但我陷入了困境 我正在尝试使用 getUserMedia 获取视频流 并通过 Socket io 将其发送到我的 Node js 服务器
  • ggplot2 多个 stat_smooth:更改颜色和线型

    我无法使用多个平滑器更改当前绘图的颜色和线型 stat smooth 这里是数据结构的概述 serviceInstanceName timestamp value 1 DE1Service utilityPredicted 2014 02
  • 如何使用 Volley 网络请求队列?

    I add 对我的 Volley 请求队列的新网络调用 我已按照建议将其创建为单例 但我总是立刻 start 这些网络调用 这始终作为活动或片段中的操作来完成 The add方法甚至不能链接到start方法 比如 add new volle
  • 数据行到字符串到数组问题

    我遇到了一个非常奇怪的问题 我有一个填充有一堆行的对象 我可以很好地访问它们 但我需要附加一个 点 到其中的每个值 因此我最终使用 foreach 循环将每个记录转换为字符串并添加一个 修剪值后 然而现在的问题是 我想将每一行 并且这些行只
  • 对于包括设置在内的小型 C 程序,合理的最小汇编指令数是多少?

    我正在尝试生成尽可能小的 C 程序 以查看通过运行它执行了多少条指令 我禁用了库的使用并禁用了 vdso 然而 根据 perf stat 我的 C 程序 gdb 说是 7 条汇编指令 最终执行了 17k 条指令 这是用于设置程序的正常指令量