Linux下一个最简单的不依赖第三库的的C程序(2)

一个最简单的C程序,如下:

main.c:

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

在64位平台上编译一个32位的程序,如下:(32位只是为了演示方便)

gcc -m32 -o hello main.c
./hello
echo $?

运行后 会看到结果是 8,说明程序正常。$?表示查看上一个命令的返回值


统计一下程序大小:7263字节,

wc -c hello
7263 hello

通过 ldd 查看动态库的依赖,发现如下的一些动态库依赖,libc.so,可以说是几乎所有Linux上的程序都会依赖的一个基础C函数库。 ld-linux.so 是一个动态dll的加载库,它会动态加载libc.so, dll在Linux shared object, 后缀是 .so linux-gate.so是一个伪文件,它是与内核联系的一个接口,64位程序的linux-vdso.so也是同样的作用

ldd hello
linux-gate.so.1 => (0xf7780000)
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf75ae000)
/lib/ld-linux.so.2 (0x5662a000)

我们加上 -nostdlib 选项,该选项在linking链接阶段,不会把标准库和启动文件引入进来,下面提示没有找到_start这个函数符号,默认给了一个入口地址。
我们不管这个警告,执行程序,发现报段错误,如果统计这个可执行文件的大小,会发现大小有1128字节,比之前的7236小了好多。

gcc -m32 -nostdlib -o hello main.c
  /usr/bin/ld: warning: cannot find entry symbol _start; defaulting to 00000000080480d8
./hello
  Segmentation fault
ldd hello
  not a dynamic executable
wc -c hello
  1128 hello

当使用 -nostdlib 编译的时候,提示没有找到_start函数符号,那我们会不会想到是glibc里面提供的某个函数呢?
Linux上的C程序,入口实际是_start,而不是我们代码里的main, _start定义在ctr1.o这个可重定位目标文件里

查找一下 ctr1.o 这个文件, 找到文件位置。
sudo find / -name "crt1.o"

只编译,不链接,-c表示只编译,生成一个32位的hello.o的目标文件

gcc -m32 -Os -c main.c -o hello.o

进行链接:

ld /usr/lib/i386-linux-gnu/crt1.o -o hello hello.o
#64位crt1.o 在/usr/lib/x86_64-linux-gnu/crt1.o

发现报错:

/usr/lib/i386-linux-gnu/crt1.o: In function `_start‘:
(.text+0xc): undefined reference to `__libc_csu_fini‘
/usr/lib/i386-linux-gnu/crt1.o: In function `_start‘:
(.text+0x11): undefined reference to `__libc_csu_init‘
/usr/lib/i386-linux-gnu/crt1.o: In function `_start‘:
(.text+0x1d): undefined reference to `__libc_start_main‘

crt1.o对应的代码在glibc的源代码,在glibc-2.19 源代码目录下的 sysdeps/i386/start.Ssysdeps/x86_64/start.S
_start 调用了__libc_start_main 等函数,__libc_start_maincsu/libc-start.c ,可以看到:

result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);

这就是main被调用的地方。这个是我们用户代码开始的地方

所以,在_startmain 之间的那些函数都是做一些glibc的准备工作,我们这里要剥离glibc的依赖,所以,我们直接可以这样做:
增加一个文件:
stubstart.s

.globl _start
_start:call main    

编译:

gcc -m32 -nostdlib -Os stubstart.s main.c -o hello
./hello #运行报错:Segmentation fault    

让我使用加上调试信息,重新编译,用gdb来看看

gcc -m32 -g -nostdlib -O0 stubstart.s main.c -o hello

首先反汇编看看,不同的编译优化,汇编代码会有一些不同:

objdump -d hello

hello: file format elf32-i386
Disassembly of section .text:
080480d8 <_start>:
80480d8:    e8 00 00 00 00       call 80480dd <main>          #等价于:push %eip   ;   mov 80480dd, %eip

080480dd <main>:
80480dd:    55                   push %ebp
80480de:    89 e5                mov %esp,%ebp
80480e0:    83 ec 10             sub $0x10,%esp               #分配临时变量
80480e3:    c7 45 fc f1 80 04 08 movl $0x80480f1,-0x4(%ebp)   #字符串赋值
80480ea:    b8 08 00 00 00       mov $0x8,%eax                #这里是把8赋值给eax,如果是把0赋值给eax,指令可能是:xor %eax,%eax 。                                                               #  xor 异或自己就是0,与 movl $0, %eax 具有类似效果
80480ef:    c9                   leave                        #等价于:mov %ebp,%esp    ;    pop %esp ;                                                               #  同时esp自动加一个步进(往栈基移动一个步进),                                                              #  即80480dd地址后的2个指令反向执行,回归原位
80480f0:    c3                   ret                          #等价于 pop %eip,是call指令的反向执行,回归原位

我们用gdb来看看

gdb ./main
gdb> break _start
gdb> run    

到_start 函数命中断点,开始 stepi 单步的执行汇编指令。

程序从_start开始,调用mainmain函数从上到下执行,从地址80480dd执行到80480f0call返回后,下一条指令又是80480dd,又会调用main,从地址80480dd执行到80480f0 又执行一遍,第二次执行,其实不是本意,
只是刚好call后面的指令恰好是main函数,一直在执行到ret指令,这时,栈顶的值是0x1, 把该值给eip0x1地址上无有效指令,报段异常,正确的段应该在80480f0附近

这里主要就是因为 call返回后,下一条指令又是80480dd,恰好是main函数,gcc编译的时候,如果使用 -Os 选项,得到的反汇编如下,这时就不会执行2次,但是依然会报段错误,因为指令指针已经飞到其他地方去了。

080480d8 <main>:
80480d8:    55             push %ebp
80480d9:    b8 08 00 00 00 mov $0x8,%eax
80480de:    89 e5          mov %esp,%ebp
80480e0:    5d             pop %ebp
80480e1:    c3             ret
080480e2 <_start>:
80480e2:    e8 f1 ff ff ff call 80480d8 <main>

正确的做法就是:在call main之后,退出进程。

.globl _start
_start:  call main
         mov $01, %eax
         mov $00, %ebx
         int $0x80

调用系统调用_exit(0);正确退出。

反汇编如下:

080480d8 <_start>:
80480d8:    e8 0c 00 00 00      call 80480e9 <main>
80480dd:    b8 01 00 00 00      mov $0x1,%eax
80480e2:    bb 00 00 00 00      mov $0x0,%ebx
80480e7:    cd 80               int $0x80    #END

080480e9 <main>:
80480e9:    55                   push %ebp
80480ea:    89 e5                mov %esp,%ebp
80480ec:    83 ec 10             sub $0x10,%esp
80480ef:    c7 45 fc fd 80 04 08 movl $0x80480fd,-0x4(%ebp)
80480f6:    b8 08 00 00 00       mov $0x8,%eax
80480fb:    c9                   leave
80480fc:    c3                   ret    

如果用C语言来代替之前那个_start的汇编,则如下:
stubstart.c

void _start() {

 // main body of program: call main(), etc

     // exit system call
      asm("mov $1,%eax;"
          "xor %ebx,%ebx;"
          "int $0x80"
          // xor 异或自己就是0,与 movl $0, %eax 具有类似效果
      );
}    

至此,一个完全不依赖第三方库的简单C函数就完成了。

参考:https://blogs.oracle.com/linux/hello-from-a-libc-free-world-part-1-v2
           https://blogs.oracle.com/linux/hello-from-a-libc-free-world-part-2-v2

原文地址:https://www.cnblogs.com/softfair/p/hello-from-a-glibc-free-world.html

时间: 2024-08-29 01:14:14

Linux下一个最简单的不依赖第三库的的C程序(2)的相关文章

Linux下一个php+mysql+nginx构建编译(三)

在此之前一直是一个关键构建webserver.但一个关键的建筑环境都比较旧的.假定使用一个相对较新的环境,尤其是正式的server.您必须手动编译自己建(基于以下的结构linux centos6.5 32地点server). 三.安装php 进入安装文件夹: cd /opt 下载并解压: Wget http://am1.php.net/get/php-5.4.34.tar.gz/from/this/mirror tar -zxf php-5.4.34.tar.gz 进入文件夹编译: cd php

Linux下一个简单的日志系统的设计及其C代码实现

1.概述 在大型软件系统中,为了监测软件运行状况及排查软件故障,一般都会要求软件程序在运行的过程中产生日志文件.在日志文件中存放程序流程中的一些重要信息, 包括:变量名称及其值.消息结构定义.函数返回值及其执行情况.脚本执行及调用情况等.通过阅读日志文件,我们能够较快地跟踪程序流程,并发现程序问题. 因此,熟练掌握日志系统的编写方法并快速地阅读日志文件,是对一个软件开发工程师的基本要求. 本文详细地介绍了Linux下一个简单的日志系统的设计方法,并给出了其C代码实现.本文为相关开发项目Linux

Linux下使用pdb简单调试python程序

python自带调试工具库:pdb # -*- coding:utf-8 -*- def func(num): s = num * 10 return s if __name__ == '__main__': print 'debug starting...' print '*' * 10 print 'debug ending-' num = 100 s = func(num) print s 在python文件中不引用pdb库,可以在执行python文件的时候,加上参数: python -m

Linux 下一个很棒的命令行工具

导读 Taskwarrior 是 Ubuntu/Linux 下一个简单而直接的基于命令行的 TODO 工具.这个开源软件是我曾用过的最简单的基于命令行的工具之一.Taskwarrior 可以帮助你更好地组织你自己,而不用安装笨重的新工具——这有时丧失了 TODO 工具的目的. Taskwarrior是一个开源.跨平台.基于命令行的 TODO 工具,它帮你在终端中管理你的 to-do 列表.这个工具让你可以轻松地添加任务.展示列表.移除任务.而且,在你的默认仓库中就有,不用安装新的 PPA.在 U

Memcahce(MC)系列(两)Linux下一个Memcache安装

Linux下一个memcache安装 memcache是高性能.分布式的内存对象缓存系统,用于在动态应用中降低数据库负载.提升訪问速度.眼下用memcache解决互联网上的大用户读取是很流行的一种使用方法,在互联网企业中有着广泛的应用. ?关于memcache的具体介绍,有兴趣的朋友请參考这篇文章:http://blog.csdn.net/xifeijian/article/details/21994941 1.下载memcache 墙裂推荐官方下载(http://memcached.org/)

linux 下一个 osw先从操作系统和标准脚本主动发起

linux 下一个 osw与操作系统的引导和启动标准的脚本.osw它指的是--os watcher,这是一个显示器os这些指标shell脚本.osw监测数据一般使用oracle技能评估os资源的使用,用法将作为oracle 有些技术人员考核oracle 数据库行为(比方节点驱赶等)的參考根据. [[email protected] ~]$ cat /etc/rc.local #!/bin/sh # # This script will be executed *after* all the ot

14行脚本配置Linux下一个Java环境变量

供Java人们刚开始学习.多半Java它需要花费大量的精力在开发环境的配置,于Linux下一个,构造Java环境变量,很可能加入这一努力. 为此,我做了一个bash脚本来配置自己主动Java环境变量.你只需要的例子,下面的脚本,你下载jdk压缩成一个目录.该脚本.并根据提示输入一些信息就可以. config_java.sh #!/bin/bash jdkpath=/usr/lib/java/ sudo mkdir $jdkpath read -p "Please input the name o

linux下一个有意思的问题(文件名以短划线或空格开头)

linux下一个有意思的问题(文件名以短划线开头) 这本是无意中的一个发现. 在linux下,文件名中含有 - 是没有问题,但是如果文件名是以-作为第一个字符的,那么就比较麻烦了. 问题演示 看这里,以短划线开头的文件名似乎对mv cp  rm   等操作免疫了.利用这个特性可以制造出一些比较麻烦的文件或者文件夹来. [email protected]:~$ ls -python.md [email protected]-pc:~$ mv -python.md 5-python.md mv:无效

linux下一个网卡绑定多个ip

[[email protected] ~]# cd /etc/sysconfig/network-scripts/ [[email protected] network-scripts]# cp ifcfg-eth0 ifcfg-eth0:1 [[email protected] network-scripts]# vim ifcfg-eth0:1 DEVICE=eth0:1HWADDR=00:0C:29:73:E6:0DTYPE=EthernetUUID=98b5dc88-94c0-4a78-