Linux下C程序的链接过程

今天看到一个很有意思的小程序,它让我对Linux下C程序的编译链接有了一个全新的认识!

这个程序的就是写一个简单的输出“hello World!”:

  要求:1.不使用C运行库,写一个独立于任何库的程序。(也就是说我们不能#include<stdio>)。

      2.不适用main函数为程序的入口(大家都知道一般使用了库的程序都是使用main函数作为程序的入口,在这里我们使用自己写的函数nomain作为程序的入口)。

      3.使用连接器ld把程序的所有段合为一个“Tinytext”段。

  显然要符合要求我们就只能用汇编去写,源代码如下:

char *str="Hello world!\n";

void print()
{
    asm("movq $13,%%rdx \n\t"
        "movq %0,%%rcx \n\t"
        "movq $0,%%rbx \n\t"
        "movq $4,%%rax \n\t"
        "int $0x80     \n\t"
        ::"r"(str):"edx","ecx","ebx");
}

void exit()
{
   asm("movq $42,%rbx   \n\t"
       "movq $1,%rax    \n\t"
       "int $0x80       \n\t");
}

void nomain()
{
   print();
   exit();
}

   我们vi一个TinyHelloWorld.c。里面写上如上代码。

本人的环境是64位的,如果是32为环境的朋友只要把,movq改为movl,rdx改为edx,rax改为eax,rbx改为ebx。

由于操作系统的系统结构发生了变化,在x86-64中,所有通用寄存器(GPRs)都从32位扩充到了64位,名字也发生了变化。8个通用寄存器(eax, ebx, ecx, edx, ebp, esp, esi, edi)在新的结构中被命名为rax, rbx, rcx, rdx, rbp, rsp, rsi, rdi。movl命令也需相应改成movq

首先程序的入口时nomain函数,它调用print函数打印HelloWorld,然后调用exit函数退出。print函数使用了Linux的WRITE系统调用,exit函数使用了EXIT系统调用。

定义一个字符串,它是放在只读代码段的。

print函数和exit函数中,WRITE系统调用和EXIT系统调用都是通过0x80中断实现的。其中eax为调用号rbx,rcx,rdx是一些寄存器用来传递参数的。比如WRITE系统调用是往一个文件句柄写入数据,如果用C语言来表示WRITE系统调用的话他的函数原型是:

int write(int filedesc,char* buffer,int size)
  • WRITE的调用号为4,则rax为0。
  • filedesc表示被写入的句柄文件,我们默认输出为默认终端,他的句柄为0,所以rbx为0。
  • buffer表示要写入的缓冲区地址使用rcx寄存器传送,我们要输出str字符串,所以rcx=str。
  • size表示要写入的字节数,这里str的长度为13字节,所以rdx=13。

代码写好以后我们使用普通的命令行来编译链接TinyHelloWorld.c

gcc -c -fno-builtin TinyHelloWorld.c  //生成TinyHelloWorld.o
  • -c参数是表示编译。
  • -fno-builtin参数关闭GCC内置函数的功能(GCC中有很多内置的函数,它你会把C库的一些函数替换成内置的函数,以达到优化的功能)

    然后链接TinyHelloWorld.o文件生成可执行文件TinyHelloWorld.

ld -static -e nomain -o TinyHelloWorld TinyHelloWorld.o
  • -static参数指在这里使用静态链接方式。
  • -e nomai参数表示程序的入口为nomain函数
  • -o 表示链接

    运行可以输出Hello World!

至此,一个HelloWorld程序写好了。

我们使用objdump指令来查看TinyHelloWorld这个文件可以看出来他又4个段:.data .rodata .text .commond 段。这几个段的属性都是只读的,按理来说我们可以把他们合围一个段。这就需要借助ld链接器来实现。

objdump -h TinyHelloWorld

使用-verbose查看默认的链接脚本信息:

ld -verbose TinyHelloWorld

然后编写TinyHelloWorld.lds脚本:

ENTRY(nomain)

SECTIONS
{
  .= 0x08048000 + SIZEOF_HEADERS;
  tinytext : { *(.text) *(.data) *(.rodata)}
  /DISCARD/ :{ *(.comment) }
}

然后使用自己编写的脚本链接目标文件。

gcc -c -fno-builtin TinyHelloWorld.
ld -static -T TinyHelloWorld.lds -o TinyHello TinyHelloWorld.o

-static -T TinyHelloWorld.lds参数是使用静态链接,并且使用自己编写的TinyHelloWorld.lds脚本链接。

它会生成一个588个字节的可执行文件TinyHelloWorld。执行可以打印Hello World!。如果你使用objdump查看TinyHelloWorld的段,你会发现我们达到了将最后一个只有一个段的要求。

时间: 2024-10-12 17:38:04

Linux下C程序的链接过程的相关文章

linux下c程序的链接、装载和库(1)

读完<程序员的自我修养--链接.装载和库>相关章节,想来总结一下,若有错误,请指正,多谢. 1. 什么叫目标文件? 你的工程里有很多xxx.c这样的源文件,这些文件是文本文件,只有人能够认识(当然编译器认识),但是,cpu可不认识.问题就是,真正执行指令的是cpu. 让编译器翻译一下(这里面有很多过程,这不是这篇文章的重点),一般来说,一个xxx.c文件就能翻译成一个xxx.o,这就是目标文件了. 一个源文件就对应一个目标文件,这个目标文件就存储了有关这个源文件的所有信息了,包括在这个源文件里

linux下c程序的链接、装载和库(3)

9. 目标文件放在一起-->静态库. 你的同事给出的目标文件太多了,从 one.o two.o …… …… 一直到 xxx.o. 好的,你如果真正想用,你的同事提供的这些现有的目标文件,你得做三件事: 一,查看自己的源文件,看看用到了哪些函数: 二,明确这些函数来自于哪个目标文件,本例中,很容易,因为函数的名字和目标文件的名字是对应的,比如one.o就会提供一个名称为one的函数: 三,使用 ld ,将你自己的目标文件和第二步中找到的那些目标文件链接起来,生成可执行文件. 第一步就是个很耗时的过

linux下c程序的链接、装载和库(2)

5. 重定义错误. 一个最终的可执行文件里,绝对不允许出现两个同名的全局变量,也不允许出现同名的全局函数. 全局函数:只要不用 static 修饰符修饰的函数,全部都是全局的. 全局变量:函数外声明定义,且不加 static 修饰符修饰的变量. 例如,one.c 里有一个函数 function, 那么你如果想让 main.c 生成的 main.o 能够链接 one.o 的话,那么 main.c 里就不能再有一个函数叫做 function 了.否则就会报重定义错误. 这就好像,你的班上有两个人都叫

Linux下软链接与硬链接的区别

Linux下软链接与硬链接的区别 Linux中的文件都文件名和数据,在linux上面被分为两个部分:元数据与数据.用户数据,即文件数据块(data block),数据块是记录文件真实内容的地方,而元数据是文件的附加属性,如大小,创建时间,所有者等信息.在Linux中,元数据中的inode号(inode是文件的元数据的一部分,但其不包含文件名,inode号即索引节点号)才是文件的唯一标识而不是文件名.文件名仅是为了方便人们的记忆和使用,系统或程序通过inode号寻找正确的文件数据快.下图为程序通过

Linux下的程序包管理之源码形式

 Linux下程序包管理之源码形式 程序包的前世今生: 说到程序包管理,不得不提到是就是程序包是由什么组成的?也就是怎么形成的?程序是由源代码程序经过预处理.编译.然后汇编形成二进制的程序,这是针对特定硬件而形成的程序.有计算机编程基础的同学都应该知道源代码编译的时候是要调用特定的库(库文件),而这些库,在不同的系统上是不同的,比如Linux和window上的就不同,不同发行版的Linux上的库也不尽相同,所以这就导致了在不同种类系统上编译生成的二进制程序的运行环境也不尽相同,那么这些程序是不能

Linux下动态链接库和静态链接库问题

第一部分:编译过程 先了解一下linux下C代码的编译过程,C代码的编译,一般分成四个阶段,包括:预编译,编译,汇编和链接,这四个阶段的分工是 预编译阶段: 编译阶段: 汇编阶段: 链接阶段: 第二部分:动态链接库和静态链接库的定义 第三部分:动态链接库和静态链接库的区别 第四部分:动态链接库和静态链接库的使用办法

Linux下c++程序内存泄漏检测代码范例

Linux下对于程序内存泄漏检测的方法很多,最常用的的莫过于使用valgrind工具.但是valgrind相当于让程序在虚拟机中运行,会带来较大的系统资源开销,还会对程序的运行效率产生较大影响,对于那种资源占用大的程序,如果需要长时间运行才能暴露的泄漏问题,它就显得不太好用. linux下的c++程序中自己实现一个轻量级的泄漏检测代码其实是比较方便的,下面我就给出一个简单的范例,并作简单的说明.当然,我们还是应该提倡使用共享指针,用共享指针自动管理内存可以避免内存泄漏这样的不必要的麻烦. 基本原

windows下QT前台和linux下后台程序通过socket通信

通常情况下,linux下的后台程序不需要GUI进行展示,而前台程序往往有个界面,方便和用户的交互.本文所演示的例 子,是QT 程序和后台linux进程(C语言)交互,通过socket传输的内容是结构体.因为QT本身是跨平台的框架,因此以后前端程序移植到其它平台依然能很好 的运行. 结构体的定义如下: struct Test              {                      int a;                      char b;              };

linux下java程序与C语言程序通过SOCKET通信的简单例子

linux下java程序与C语言程序通过SOCKET通信的简单例子 今天上午实验了java程序与c语言程序通过socket进行通信.由于没学过java,因此只是编写了C语言端的代码,java端的代码是从网上别的文章中找的,经过少量修改后与C语言端程序通信成功. 本例中C语言端作为服务器,java端作为客户端 代码如下: /****************** server program *****************/ #include <stdio.h> #include <sy