一、需求
日常的HelloWorld打印,需要用到C语言库里面的函数(printf),我们希望“小程序”脱离C语言库,使它成为一个独立于任何库的纯正“程序”
日常使用的程序,都是以main作为入口,现在我们自定义一个入口(nomain)
日常的HelloWorld会产生很多段,现在我们需要将所有的段合并到"tinytext"段
二、实现
2.1GCC内嵌汇编
int write(int filedesc, char* buffer, int size); 函数实现!!!
- WRITE 调用号为4
- filedesc表示被写入文件句柄,使用ebx寄存器传递,这里我们需要默认终端输出,它的句柄为0,ebx = 0
- buffer表示要写入缓冲区地址,使用ecx寄存器传递,我们这里要输出字符串str,所以ecx = str
- size 表示写入的字节数,使用edx寄存器传递,字符串str的长度为13, 所以edx = 13
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");
}
2.2结束进程
ebx 表示进程退出码(Exit Code)
EXIT系统调用的调用号为1,即eax = 1
void
exit()
{
asm( "movl $42, %ebx \n\t"
"movl $1, %eax \n\t"
"int $0x80 \n\t");
}
备注:
当执行完程序之后,如果想打印程序执行的退出码,可以这样子操作:
$ echo $?
三、实现
/*
* ld.c
* Copyright (C) 2020 root <root@tw.com>
*
* Distributed under terms of the MIT license.
*/
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();
}
四、编译&链接
$ gcc -c -fno-builtin ld.c
$ ld -static -e nomain -o ld ld.o
4.1常见错误
原因:
在64位系统下去编译32位的目标文件,这样是非法的。
解决办法:
用”-m32”强制用32位ABI去编译
$ gcc -c -fno-builtin -m32 ld.c
4.2 链接错误
原因:
目标文件`ld.o’是32位系统的,在x86_64平台下默认链接用的是elf_x86_64.x,链接32位可执行文件使用的是elf32_x86_64.x
解决办法:
$ ld -static -m elf_i386 -e nomain -o ld ld.o
五、执行
六、改进
将多个端合并为一个段 !!!
// ld.lds
ENTRY(nomain)
SECTIONS
{
. = 0x08048000 + SIZEOF_HEADERS;
tinytext : { *(.text) *(.data) *(.rodata) }
/DISCARD/ : { *(.comment) }
}
- ENTRY()指定程序入口
- SECTIONS链接脚本主体
- . = 0x08048000 + SIZEOF_HEADERS; . 表示当前虚拟地址;SIZEOF_HEADERS;为输出文件的文件头大小。 这里表示 将当前的虚拟地址设置成0x08048000 + SIZEOF_HEADERS 。 因为这条语句后面紧跟着输出段"tinytext",所以此段虚拟地址即为:0x08048000 + SIZEOF_HEADERS
- tinytext : 转换规则。表示合并到tinytext
- /DISCARD/ 丢弃.comment段
$ ld -static -T ld.lds -m elf_i386 -o ld ld.o