本文将综合以下4篇文章,学习如何写出不依赖libc库的程序:

  • 【软件开发底层知识修炼】九 链接器-可重定位文件与可执行文件
  • 【软件开发底层知识修炼】十 链接器-main函数不是第一个被执行的函数
  • 【软件开发底层知识修炼】十一 链接器-链接脚本
  • 【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)

如果没有看上面4篇文章的,建议先按照顺序学习上述4篇文章,再来看这篇文章,不然有些地方会很突兀。

文章目录

  • 1 本篇文章的目的
  • 2 解决方案设计
  • 3 拓展
  • 4、总结

1 本篇文章的目的

那么本篇文章的目的是什么呢?

  • 我们想编写一个体积非常小的可执行程序。
  • 通过makefile完成代码编译
  • 运行后在屏幕上打印“D.T.SoftWare”

但凡是学习过C语言基础语法的人都能写出来这个程序 ,这不就是一个"hello word " 程序么?就像下面这样.

hello.c

#include <stdio.h>int main(){printf("D.T.SoftWare\n");return 0;
}

我们编译上述hello.c,得到可执行程序hello,可以看出hello的大小是:

竟然有7135字节!!!这其实是因为虽然我们只写了个printf函数,但是实际上在编译链接的过程,链接器将一大堆库函数都与我们的hello程序进行链接,包括一些入口函数啊,进程初始化函数以及printf这个库函数等等,看起来只有一个printf库函数,但是实际上一与libc库进行链接,就会有一大堆东西(这些东西是啥吗,请参看上述四篇文章)。

所以,我们的目的,并不是简单的写出上面的hello.c程序。我们想写一个程序,进行编译链接后,体积达到最小?该如何做到?

我们的分析思路大概如下图:

  • 我们知道main函数执行前还有一大堆的初始化函数需要调用,我们不依赖于libc库进行编译链接,以及编写自定义入口函数的链接脚本可以实现
  • 不依赖libc库打印字符串的话,就直接进行系统调用,直接调用sys_write函数,而不是调用printf函数
  • 直接调用sys_write函数的方法的话就是使用内嵌汇编语言进行调用

学过我上面篇文章的朋友就一定会发现上述的几点,都在那4篇文章中实现过。所以我们可以很轻松的进行代码的编写。

2 解决方案设计

上面已经给出了基本的程序设计思路,现在我们来给出更加具体的程序设计思路。

  • 通过内嵌汇编自定义打印函数和退出函数(具体来说就是使用INT 0X80指令)
  • 通过编写自定义的链接脚本来指定我们自己的入口函数(不依赖任何库和GCC的任何内置功能)
  • 删除可执行程序的无用信息,比如无用的调试信息和段信息。这是通过链接脚本指定的

那么我们先来通过内嵌汇编设计一下打印函数和退出函数,这里要参考【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)。

  1. 打印函数print:
void print(const char* s, int l)
{asm volatile ("movl $4, %%eax\n"     // sys_write系统函数相关"movl $1, %%ebx\n""movl %0, %%ecx\n""movl %1, %%edx\n""int $0x80     \n"    //通过80H进行系统调用:: "r"(s), "r"(l)      // print的参数: "eax", "ebx", "ecx", "edx"  // 保留列表);
}

上述打印函数在【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)这篇文章中一讲非常详细。

  1. 退出函数 exit
void exit(int code)
{asm volatile ("movl $1, %%eax\n"   //sys_exit"movl %0, %%ebx\n""int $0x80     \n":: "r"(code)        //参数: "eax", "ebx");
}

上述退出函数也在【软件开发底层知识修炼】十二 C/C++语言中内嵌汇编语言(asm)这篇文章中一讲非常详细。

  1. 链接脚本设计
    program.lds

上述链接脚本中已经注解的非常详细,当然,还是需要参考【软件开发底层知识修炼】十一 链接器-链接脚本这篇文章先学习以下链接脚本的语法与概念最好。

那么上述程序设计基本上完成,下面我们给出完成的代码:

  • program.c 源文件
void print(const char* s, int l);
void exit(int code);void program()
{print("D.T.Software\n", 13);exit(0);
}void print(const char* s, int l)
{asm volatile ("movl $4, %%eax\n""movl $1, %%ebx\n""movl %0, %%ecx\n""movl %1, %%edx\n""int $0x80     \n":: "r"(s), "r"(l): "eax", "ebx", "ecx", "edx");
}void exit(int code)
{asm volatile ("movl $1, %%eax\n""movl %0, %%ebx\n""int $0x80     \n":: "r"(code): "eax", "ebx");
}
  • program.lds 链接脚本
ENTRY(program)SECTIONS
{.text 0x08048000 + SIZEOF_HEADERS :{*(.text)*(.rodata)}/DISCARD/ :{*(*)}
}
  • 当然,为了编译方便,我还给出了makefile文件,方便我们程序的编译:

CC := gcc
LD := ld
RM := rm -frTARGET := program.out
SRC := $(TARGET:.out=.c)
OBJ := $(TARGET:.out=.o)
LDS := $(TARGET:.out=.lds).PHONY : rebuild clean all$(TARGET) : $(OBJ) $(LDS)$(LD) -static -T $(LDS) -o $@ $<@echo "Target File ==> $@"$(OBJ) : $(SRC)$(CC) -fno-builtin -o $@ -c $^rebuild : clean allall : $(TARGET)clean :$(RM) $(TARGET) $(OBJ)

上述makefile可能大多数人看不懂,这个无所谓,它只是一种类似于脚本语言的语言,辅助我们编译程序的,我们将上述三个文件:makefile program.lds program.c这三个文件放到linux系统下的同一个目录下,然后输入命令make即可完成代码的编译,生成可执行文件。

  • 就像下面这样,我们队我们的程序进行编译

  • 运行可执行程序 ./program.out

很明显,我们实现了我们最开始的功能。

  • 我们看看我们写的这个可执行程序的大小:

  • 上面的是hello的大小,下面的是我们自己的可执行程序的大小,只有582字节,远远小于hello的大小。这正是我们所希望看到的。

3 拓展

如果有详细看上述的makefile文件,我们会发现,在我们编译我们的源文件的时候,使用了静态链接。现在在这里介绍一些链接时的一些选项:

  • ld 命令

    • GNU的链接器,将目标文件链接为可执行文件
  • ld -static

    • 表示ld使用静态链接的方式来产生最终的可执行程序,而不是默认的动态链接。至于什么是静态链接什么是动态链接,后面肯定会有文章详细学习。
  • gcc -fno-builtin

    • -fno-builtin 用于关闭GCC内部函数功能
    • GCC提供了很多内置函数(Built-in Function),它会把一些常用的C库函数替换成编译器内置的函数,以达到优化程序的目的。在上述我们的makefile中就用到了这个选项,以防止编译器的优化

4、总结

  • 对于资源受限制的嵌入式设备,需要考虑可执行程序的大小(当然目前各种设备内存都足够大,不必担心这一点)

  • 通过在C/C++语言中内嵌汇编语言,可以避免使用库函数而直接使用系统函数

  • 可以通过如下方式,来控制可执行程序的大小

    • 最小化库的使用(必要的时候,可以自己实现相关函数)
    • 自定义链接脚本,删除无用的段信息

本文参考狄泰软件学院相关课程
想学习的可以加狄泰软件学院群,
群聊号码:199546072

学习探讨加个人(可以免费帮忙下载CSDN资源):
qq:1126137994
微信:liu1126137994

【软件开发底层知识修炼】十三 链接器-如何写出不依赖C库函数的代码相关推荐

  1. 【软件开发底层知识修炼】二十三 ABI-应用程序二进制接口三之深入理解函数栈帧的形成与摧毁

    上两篇文章我们初步接触了ABI-应用程序二进制接口的概念,点击链接查看上一篇文章:[软件开发底层知识修炼]二十二 ABI-应用程序二进制接口 二.了解了为什么会有ABI的存在.本篇文章继续学习ABI ...

  2. 【软件开发底层知识修炼】二十七 C/C++中的指针与数组是不同的

    上几篇文章学习了ABI-应用程序二进制接口:[软件开发底层知识修炼]二十六 ABI-应用程序二进制接口 学习总结文章目录 本篇文章就指针与数组的联系与区别来学习学习 文章目录 1 疑问 2 指针与数组 ...

  3. 【软件开发底层知识修炼】二十四 ABI之函数调用约定

    上一篇文章学习了Linux环境下的函数栈帧的形成与摧毁.点击链接查看相关文章:软件开发底层知识修炼]二十三 ABI-应用程序二进制接口三之深入理解函数栈帧的形成与摧毁 本篇文章继续学习ABI接口相关的 ...

  4. 【软件开发底层知识修炼】十四 快速学习GDB调试一 入门使用

    前面几篇文章学习了链接器相关的内容.现在开始来学习GDB调试.我们的目的是通过这几篇文章将GDB调试完全学会. 文章目录 1 为什么需要GDB 2 GDB 的常规应用 3 GDB调试程序实例 4 总结 ...

  5. 【软件开发底层知识修炼】二十八 C/C++中volatile的作用

    上一篇文章学习了C/C++中的指针与数组的区别,点击链接进行查看:[软件开发底层知识修炼]二十七 C/C++中的指针与数组是不同的 本篇文章将学习volatile关键字在C/C++中的作用 文章目录 ...

  6. 【软件开发底层知识修炼】二十六 ABI-应用程序二进制接口 学习总结文章目录

    前面学习了ABI的知识,感觉受益良多.对底层与编译器有更加深刻的认识,为此这里将前面写过的关于ABI 的文章给列出来,方便学习与翻阅. [软件开发底层知识修炼]二十一 ABI-应用程序二进制接口一 [ ...

  7. 【软件开发底层知识修炼】二十五 ABI之函数调用约定二之函数返回值为结构体时的约定

    上一篇文章学习了几种函数调用约定的区别,点击链接查看上一篇文章:[软件开发底层知识修炼]二十四 ABI之函数调用约定 本篇文章继续学习函数调用约定中,关于函数返回值的问题.当函数返回值为结构体时,函数 ...

  8. 【软件开发底层知识修炼】二十二 ABI-应用程序二进制接口 二

    上一篇文章学习了ABI的相关内容,具体最后分析了不同ABI下结构体的对齐方式的不同.点击链接查看上一篇文章:[软件开发底层知识修炼]二十一 ABI-应用程序二进制接口一 本篇文章继续学习ABI相关内容 ...

  9. 【软件开发底层知识修炼】二十 深入理解可执行程序的结构

    上一篇文章记录了GDB调试从入门到熟练掌握的学习全过程.点击链接查看:[软件开发底层知识修炼]十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录 还记得在以前的学习Binutils工具的时候,学 ...

最新文章

  1. 我对于js注入的理解
  2. Xilinx SelectIO 接口
  3. zabbix 3.4 ubuntu 16 用腾讯企业邮箱作为告警邮箱
  4. 1.关于逆向工程(RE、RCE)-笔记
  5. Apache服务器二级域名的完美实现
  6. VTK:Utilities之TimeStamp
  7. CentOS安装php mbstring的扩展
  8. C-指针02 2017/11/24
  9. [Aaronyang] 写给自己的WPF4.5 笔记17[Page实现页面导航]
  10. 让Python在后台自动解压各种压缩文件!
  11. rostcm6情感分析案例分析_微博分析-内容分析系统 ROST CM 6 使用手记
  12. SMBIOS介绍(3):实现
  13. 互联网协议 — ECMP 等价多路径路由
  14. 【算法与数据结构】—— 大数运算
  15. 电商项目数据库设计 | 参考京东商城详细讲解商品数据库设计
  16. nanomsg接口函数以及功能列举大全
  17. 求通俗讲解下tensorflow的embedding_lookup接口的意思
  18. carry函数在C语言中用法,常考词的语法与用法:careless, carry, case
  19. 7个免费的在线音频编辑网站推荐
  20. 电脑计算机打不开显示远程过程,win10系统打不开图片提示“远程过程调用失败”的解决方法...

热门文章

  1. java学习(52):抽象类
  2. Python3 isspace()方法
  3. linux文件权限报错实例,自定义系统service SELinux权限报错
  4. Nginx快速搭建和基本使用
  5. python定义一个列表_如何在Python中创建用户定义的列表?
  6. CSAPP:第十一章 网络编程
  7. 关于CaciiEZ端口流量阀值报警的设置
  8. MATLAB入门学习(三)
  9. t-sql中的事务控制及错误处理
  10. win2003 定时重启BAT