编译、链接一个小程序

12 篇文章 0 订阅
11 篇文章 0 订阅
本文详细介绍了如何使用汇编语言实现输出"helloworld"并退出的程序,通过分析编译和链接过程,展示了如何将C代码转换为汇编,并最终生成ELF可执行文件。过程中涉及到了Linux的WRITE和EXIT系统调用,以及汇编指令的使用。同时,文章还探讨了ELF文件的结构,包括节区、段、符号表和入口点等关键信息,以及静态链接和动态链接中的符号表作用。此外,还提到了常见的编译和链接错误以及解决方法。
摘要由CSDN通过智能技术生成
源代码

输出"hello world",然后退出;
vim a.c

char * str = "hello world\n";
void print(){

	asm("movl $13,%%edx \n\t"
	"movl %0,%%ecx \n\t"
	"movl %0,%%ebx \n\t"
	"movl %4,%%eax \n\t"
	"int $0x80 \n\t"
	::"r"(str):"edx","ecx","ebx");
	
}

void exit(){
	asm("movl $42,%ebx \n\t"
	   "movl $1,%eax \n\t"
	   "int $0x80 \n\t");
}

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

程序“倒着读”;入口是nomain函数,调用print和exit函数,前者是输出hello world(write),后者是退出(exit)。
print函数:执行linux WRITE system Call;通过0x80中断(应该软中断),写入(CPU)参数(eax是调用,ebx,ecx,edx都是通用寄存器,传递参数);
exit函数:执行EXIT system Call;ebx是退出码(42),比如常见exit(0),退出码就是0;

参考GCC内嵌汇编手册
参考计算机组成原理的实验 X86汇编指令

编译

不使用gcc内置函数编译(再汇编)为汇编文件;(为链接做准备)

gcc -m32  -o a.o -c -fno-builtin a.c

gcc 编译器(内嵌/集成了汇编器)就不说了;
-c 参数;-fno-builtin 不使用内置函数(built-in Function);
-o 输出文件
输入文件

链接

参考编译过程,指定输入输出,指定编译环境(机器)架构,生成可执行文件(链接目标文件);

ld -static -e nomain -m elf_i386 -o a a.o

ld链接器
-static 静态链接
-e 入口函数,默认是main;
-m i386 architecture

总结

查看本次实验生成的ELF可执行文件;
可以看到有很多段/节(包含ELF头,魔数、ABI规范、每个段的起始地址、是否是有效文件):
.text 程序指令
.rodata 数据,保存的是“hello world”
.data (强符号)str全局变量(对比前面源码)
.comment 编译器、系统版本信息(比如使用的是elf_i386而不是i386x86 architecture)
.systab (全局)符号表,非常重要(链接器的核心“成果”)
还有其他段/节…

readelf -e a
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x80480c6
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4540 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         3
  Size of section headers:           40 (bytes)
  Number of section headers:         9
  Section header string table index: 8

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        08048094 000094 000042 00  AX  0   0  1
  [ 2] .rodata           PROGBITS        080480d6 0000d6 00000e 00   A  0   0  1
  [ 3] .eh_frame         PROGBITS        080480e4 0000e4 00007c 00   A  0   0  4
  [ 4] .data             PROGBITS        0804a000 001000 000004 00  WA  0   0  4
  [ 5] .comment          PROGBITS        00000000 001004 00002c 01  MS  0   0  1
  [ 6] .symtab           SYMTAB          00000000 001030 000100 10      7   9  4
  [ 7] .strtab           STRTAB          00000000 001130 000049 00      0   0  1
  [ 8] .shstrtab         STRTAB          00000000 001179 000042 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08048000 0x08048000 0x00160 0x00160 R E 0x1000
  LOAD           0x001000 0x0804a000 0x0804a000 0x00004 0x00004 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .eh_frame 
   01     .data 
   02   

查看一些其他信息

目标文件格式分析

nm a
0804a004 D __bss_start
0804a004 D _edata
0804a004 D _end
080480b4 T exit
0804a000 d _GLOBAL_OFFSET_TABLE_
080480c6 T nomain
08048094 T print
0804a000 D str

还有 readelf -S / -s 等等;objdump 、size 等工具,建议都试下,能看到很多有意思的东西。

另外可以丢弃一些编译段(比如.comment,类似程序的注释)
常见编译、链接错误

汇编指令错误比如mov(move)、movl(MOVEL)、

a.c: Assembler messages:
a.c:5: Error: no such instruction: 

编译环境差异
比如这里是输入是i386,但是输出是i386:x86,需要强制指定:
-m elf_i386

[ad@localhost ~]$ ld -static -e nomain -o a a.o
ld: i386 architecture of input file `a.o' is incompatible with i386:x86-64 output

比如我本地虚拟机支持的(汇编指令集)有:

Supported emulations: elf_x86_64 elf32_x86_64 elf_i386 elf_iamcu i386linux elf_l1om elf_k1om i386pep i386pe

扩展阅读
1、符号表;
它是静态链接和动态链接的基本结构,同时二者的链接原理其实就是在Symbol Table中找到未在本目标文件中定义但是却在本目标文件中使用的符号的引用地址【也就是符号值】(此外符号还有强符号和弱符号,简单理解初始化了的全局变量是强符号,未初始化的全局变量是弱符号,当然还有其他条件);在工程实践中非常紧俏比如C/C++ 的静态/动态链接库、Golang的plugin 、Java的jar、Lua的热补丁;
2、ELF文件结构
ELF header一般在/usr/include/elf.h可以看到描述和format header struct;

3、API与ABI;
API是源代码级别的接口(规范),比如POSIX;
而ABI是二进制(中间)文件的"接口"规范;比如链接时的符号定义、寄存器的定义和放置方式;
最常见的例子:
API:不同语言之间、系统间实现了POSIX就可以被广泛接受,反之不行;
ABI:不同编译器的编译过程不一样,导致的不能正常链接比如压栈的顺序就可能不一样(从右向左还是从左向右)、外部符号的存储不一样、寄存器的位置不一样、甚至内置数据类型的大小都不一样(32位和64位就不一样所以不能混用);在C++上尤其明显(连统一的ABI标准都没有),经常看到A公司的库不能被B公司使用使用他们的编译工具不同(:-;

参考与扩展阅读

GCC内嵌汇编手册
X86汇编指令
计算机组成原理
程序员的自我修养
ELF文件结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值