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

本文将综合以下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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422931.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前端学习(218):属性选择器

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/ html4/strict.dtd"> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><title>属性选择器&…

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

前面几篇文章学习了链接器相关的内容。现在开始来学习GDB调试。我们的目的是通过这几篇文章将GDB调试完全学会。 文章目录1 为什么需要GDB2 GDB 的常规应用3 GDB调试程序实例4 总结1 为什么需要GDB 什么是GDB&#xff1f; GNU项目中的调试器&#xff08;gnu debuger&#xff0…

前端学习(219):css伪类选择器

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/ html4/strict.dtd"> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><title>CSS伪类<…

【软件开发底层知识修炼】十五 快速学习GDB调试二 使用GDB进行断点调试

上一篇文章我们学习了使用GDB的最基本方法&#xff1a;【软件开发底层知识修炼】十四 快速学习GDB调试一 入门使用 本篇文章将学习GDB的断点调试。断点调试是一种非常重要的调试方法。 文章目录1 断点类型2 GDB软件断点调试相关操作2.1 通过函数名设置断点2.2 通过文件名行号…

Informix IDS 11体系操持(918测验)认证指南,第 4 部门: 机能调优(1)

对 IBM Informix Dynamic Server (IDS) 和它的分比如子体系举行调优&#xff0c;以失掉最佳机能。在一个冗长的概述之后&#xff0c;本教程给出了一些关于怎样检查数据库做事器及其子体系的例子。本文是这个分 8 部门的 系列教程 的第 4 部门&#xff0c;这个教程可以帮手您筹办…

前端学习(220):伪元素选择器

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/ html4/strict.dtd"> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><title>CSS伪元素&l…

【软件开发底层知识修炼】十六 快速学习GDB调试三 使用GDB的数据断点监测变量是否改变

上一篇文章我们学习了如何使用GDB进行软件断点调试和硬件断点调试&#xff1a;【软件开发底层知识修炼】十五 快速学习GDB调试二 使用GDB进行断点调试本篇文章继续上一篇文章的学习&#xff0c;如何使用GDB的数据断点监测内存中的变量是否被改变 文章目录1 GDB的数据断点1.1 利…

AllTray-将办法最小化到琐细托盘

Toy Posted in AppsAllTray 是一个很有效的小办法&#xff0c;操纵它你可以将办法的窗口最小化到琐细托盘&#xff0c;从而腾出桌面空间以作它用。此办法主要为那些没有原生供给最小化到琐细托盘成效的办法而预备的。今朝&#xff0c;AllTray 可以在 GNOME、KDE、Xfce、Fluxbox…

【软件开发底层知识修炼】十七 快速学习GDB调试四 使用GDB进行函数调用栈的查看

上一篇文章学习了如何使用GDB数据断点进行内存监测&#xff1a;【软件开发底层知识修炼】十五 快速学习GDB调试三 使用GDB的数据断点监测变量是否改变本篇文章继续上一篇文章的学习&#xff1a;如何使用GDB进行函数调用栈的查看 文章目录1 backtrace和frame2 使用GDB进行函数调…

前端学习(221):字体属性

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/ html4/strict.dtd"> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><title>CSS字体属性…

nload实时查看linux服务器网络流量的工具

如果你仅仅是想查询当前服务器的带宽,nload绝对是个很好用的一个工具,功能虽然很单一,但是很强.虽然不能像iptraf那样,可针对IP, 协议等条件来查询,可以实时地监控网卡的流量,分输入流量Incoming 和输出流量Outgoing两部分,同时统计当前,平均,最小,最大,总流量的值,并且用动态图…

前端学习(222):文本属性

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/ html4/strict.dtd"> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><title>CSS文本属性…

【软件开发底层知识修炼】十八 快速学习GDB调试五 使用GDB进行调试的一些小技巧

上一篇文章学习了如何使用GDB进行函数调用栈的查看&#xff1a;【软件开发底层知识修炼】十六 快速学习GDB调试四 使用GDB进行函数调用栈的查看本篇文章是GDB调试快速学习系列的最后一篇。将综合前几篇文章做一个小的总结以及介绍一些在调试中使用的小技巧 文章目录1 调试中的小…

MongoDB学习笔记(一) MongoDB介绍及安装

系列目录 MongoDB学习笔记(一) MongoDB介绍及安装MongoDB学习笔记(二) 通过samus驱动实现基本数据操作MongoDB学习笔记(三) 在MVC模式下通过Jqgrid表格操作MongoDB数据MongoDB学习笔记(四) 用MongoDB的文档结构描述数据关系MongoDB学习笔记(五) MongoDB文件存取操作MongoDB…

【软件开发底层知识修炼】十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录

本文记录之前写过的5篇关于GDB快速学习的文章&#xff0c;从第一篇开始学习到最后一篇&#xff0c;保证可以从入门GDB调试到熟练掌握GDB调试的技巧。 学习交流加 个人qq&#xff1a; 1126137994个人微信&#xff1a; liu1126137994学习交流资源分享qq群&#xff1a; 962535112 …

前端学习(224):iconfont矢量库

矢量库 一、如何下载iconfont图标。 在iconfont-阿里巴巴矢量图标库中有多种图标供你选择。比如我们选择其中的一个作为示例。 鼠标悬浮上后会出现三种选择&#xff1a; 1.添加入库&#xff1a;功能和淘宝中的购物…

如何转载别人的博客

前言 对于喜欢逛CSDN的人来说&#xff0c;看别人的博客确实能够对自己有不小的提高&#xff0c;有时候看到特别好的博客想转载下载&#xff0c;但是不能一个字一个字的敲了&#xff0c;这时候我们就想快速转载别人的博客&#xff0c;把别人的博客移到自己的空间里面&#xff0c…

Spoken English(015)

I know what you want Is that why you don’t want to go home I’m sure we can get you a great/good deal Would you help me with the report I didn’t konw he was the richest person in the world I’ll have to ask my boss/wife first I take it you don’t agree I…

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

上一篇文章记录了GDB调试从入门到熟练掌握的学习全过程。点击链接查看&#xff1a;【软件开发底层知识修炼】十九 GDB调试从入门到熟练掌握超级详细实战教程学习目录 还记得在以前的学习Binutils工具的时候&#xff0c;学习了很多工具来查看可执行程序的结构&#xff0c;那个时…

前端学习(225):尺寸属性

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/ html4/strict.dtd"> <html><head><meta http-equiv"content-type" content"text/html; charsetutf-8"><title>CSS尺寸属性…