MiniCRT 64位 linux 系统移植记录:64位gcc的几点注意
32位未修改源码与修改版的代码下载:
git clone git@github.com:youzhonghui/MiniCRT.git
MiniCRT 64位 linux 系统移植记录
MiniCRT是《程序员的自我修养:链接,转载于库》的作者俞甲子写的小型的C运行时库。里面提供了printf,malloc,free,fopen等比较常用的函数实现。
之所以要捣鼓这个东西,是因为要自己写一个链接器,链接标准库的时候出了麻烦,一些符号在整个libc中都找不到定义,标准库又太大,研究源码,翻文档都不方便,不如拿一个小巧可用的MiniCRT过来,源码在手,知根知底。
但是也不是一帆风顺,我现在用的系统是64位的archlinux,俞甲子在写书的时候用的还是32位系统。搬运到64位系统上还遇上写麻烦,但是比较64位是趋势了,不能老窝在32位里,在前人经验的庇护下学习吧。所以捣騰了一天,修改了源码,把他移植到64位的linux系统上来,这个过程也学到一些有趣的东西。下面是过程记录。
下了源码,按照readme.txt编译代码
# gcc -c -fno-builtin -nostdlib entry.c malloc.c stdio.c string.c printf.c # ar -rs minicrt.a malloc.o printf.o stdio.o string.o # gcc -c -ggdb -fno-builtin -nostdlib test.c # ld -static -e mini_crt_entry entry.o test.o minicrt.a -o test
但是在第一句的时候,entry.c就无法通过编译。错误信息:
entry.c:59: Error: unsupported instruction `mov'
打开发现错在一句内联汇编上:
//ebp_reg = %ebp asm("movl %%ebp,%0 \n":"=r"(ebp_reg));
我学汇编写汇编都是在windows下,对AT&T的汇编语法不熟,谷歌之,找到一篇好资料:
http://argcandargv.com/articles/84.c
语法上这句汇编没错,我也是在几次试验以后猛然发现指针竟然是64位的。我这才意识到我真的是在64位系统上阿(你特么不是一直在用吗 – -)。那么错误很明显了,movl 和 ebp是32位的,%0即ebp_reg是64位的。
修改为
asm("movq %%rbp,%0 \n":"=r"(ebp_reg));
编译通过。
下面一堆警告,还是64位指针惹的祸。
将所有源文件中的int换成了long,main函数的int返回类型可以保留,再编译,警告消失。
但是运行./test
意料之外,无输出。
把test.c换成了一个更简单的文件来debug
#include "minicrt.h"
int main()
{
printf("hello world\n");
return 0;
}
单步跟踪发现,int 0×80的4号中断不好使了。网上也没找到相关的信息。
我和小伙伴们都有点心灰意冷(要是64位系统不支持这个4号中断,我还搞个蛋啊!)
但是在一股不甘心的力量驱动下,又做了几次试验,把这段代码独立出来,编成32位,运行,惊奇发现,输出hello world了。
那么64位系统还是支持这个系统调用的,为什么32位可以,而64位不行?
猜测:
这个中断只能输出4GB以内地址的字符串,也就是支持ecx,但是不支持rcx。
验证的试验很容易做,发现确是是这样。
readelf -s test
一看,全局变量,静态变亮的地址都在 0×400000 – 0x60FFFFF 之内。那么能越界的就是栈中的局部变量了。
那么我必须要在调用4号中断之前,把栈里的内容拷贝到全局变量中,然后把全局变量指针交给4号中断,这样就解决越界的问题了。
修改了fputc和fputs函数:
static char __fputc_tmp_val__ = 0;
long fputc(char c,FILE* stream)
{
__fputc_tmp_val__ = c;
if (fwrite(&__fputc_tmp_val__,1,1,stream) != 1)
{
return EOF;
}
else
{
return c;
}
} static char __fputs_tmp_array__[256] = {0};
static int __fputs_tmp_size__ = 256;
long fputs(const char* str,FILE *stream)
{
long len = strlen(str);
if( len >= __fputs_tmp_size__ )
return EOF;
strcpy( __fputs_tmp_array__,str );
if (fwrite(__fputs_tmp_array__,1,len,stream) != len)
{
return EOF;
}
else
{
return len;
}
}
测试,顺利输出hello world
原以为这样就大功告成了,但是换回原来的tes进入t.c一试,又没有输出。
晕,单步!
发现参数根本没有正确传递。看反汇编:
printf调用之前
18 printf("%d %s\n",len,buf);
00000000004014db: mov -0x10(%rbp),%rdx
00000000004014df: mov -0x18(%rbp),%rax
00000000004014e3: mov %rax,%rsi
00000000004014e6: mov $0x4015f6,%edi
00000000004014eb: mov $0x0,%eax
00000000004014f0: callq 0x400e5b
进入printf
printf:
0000000000400e5b: push %rbp
0000000000400e5c: mov %rsp,%rbp
0000000000400e5f: sub $0xd0,%rsp
0000000000400e66: mov %rsi,-0xa8(%rbp)
0000000000400e6d: mov %rdx,-0xa0(%rbp)
0000000000400e74: mov %rcx,-0x98(%rbp)
0000000000400e7b: mov %r8,-0x90(%rbp)
0000000000400e82: mov %r9,-0x88(%rbp)
0000000000400e89: test %al,%al
0000000000400e8b: je 0x400ead
0000000000400e8d: movaps %xmm0,-0x80(%rbp)
0000000000400e91: movaps %xmm1,-0x70(%rbp)
0000000000400e95: movaps %xmm2,-0x60(%rbp)
0000000000400e99: movaps %xmm3,-0x50(%rbp)
0000000000400e9d: movaps %xmm4,-0x40(%rbp)
0000000000400ea1: movaps %xmm5,-0x30(%rbp)
0000000000400ea5: movaps %xmm6,-0x20(%rbp)
0000000000400ea9: movaps %xmm7,-0x10(%rbp)
0000000000400ead: mov %rdi,-0xc8(%rbp)
之前写操作系统,也自己实现过printf,但是..但是,这是妹啊!为什么参数没有通过栈传递!
找资料,同时心中默默将gcc骂了十遍。
找到一篇资料: http://blog.csdn.net/videosender/article/details/6425671
我从里面摘出比较重要的一段:
「而GCC的调用约定跟VC不同。前6个整数参数会依次放到rdi, rsi, rdx, rcx, r8, r9中,前8个浮点参数放到xmm0到xmm7中。除了使用了更多的寄存器,与vc不同的是,整数和浮点数寄存器是混合使用的不用为没用的参数预留。还是刚才的例子,第一个参数是int,第二个是double,第三个char*,第四个double,参数数会依次放到 rdi,xmm0,rsi,xmm1. 另外,没有在栈上预留寄存器区。 更多的参数和vc一样,放在栈上。」
通过试验发现,通过寄存器传递参数这个设置没办法通过__attribute__((regparm(0)))来关闭。
这样只能修改代码了。
可以看到,要实现一个寄存器参数版的va_start,va_arg,va_end比较麻烦,我又不想修改过多代码。
观察发现,在-O0优化选项下(gcc的默认选项),进入printf后,会先把rsi,rdx…这些寄存器挨个放入栈。如上面所示,不过实际传入的参数个数有多少。
但是比较奇怪的是,应该是rdi为第一个参数,但是rdi并没有出现在rsi之前。
别忘了,printf的第一个参数是显示声明的,是一个字符串,上边汇编的最后一句,mov %rdi,-0xc8(%rbp)就表明正是如此。
那么我们要的参数列表就从rsi开始,它被复制到-0xa8(%rbp)的位置。Check!这就是我们要找的位置。
另外有一点很需要注意的是,浮点参数会放到xmm0到xmm7中,从上面的汇编可以看出,rsi,rdx..xmm0…的排列顺序是固定的。在复制xmm0-xmm7之前,有一句test %al ,%al,当调用printf时,有传入浮点参数时eax=1,否则为0。超过六个的整数参数会被压入栈中。
好了,只要不传入浮点参数,那么我们就可以通过0xa8的偏移来找到arg_list。而MiniCRT的printf也没有支持浮点输出,那么,我们就取巧吧。
将printf由
int printf(const char *format,...)
{
va_list(arglist);
va_start(arglist,format);
return vfprintf(stdout,format,arglist);
}
修改为
long printf(const char *format,...)
{
char* arglist;
asm( "movq %%rbp,%0":"=r"(arglist) );
arglist -= 0xa8;
return vfprintf(stdout,format,arglist);
}
好了,再输入readme.txt里的四条命令,运行test,是不是看到输出了?
来源:http://www.tuicool.com/articles/Zzqye2
MiniCRT 64位 linux 系统移植记录:64位gcc的几点注意的更多相关文章
- win10 64位专业版系统中显示32位dcom组件配置的方法
word.excel是32位的组件,当用户64位系统在运行窗口中输入dcomcnfg命令时,在打开的组件服务管理窗口,是找不到Microsoft Excel.word程序的.另外,Windows 环境 ...
- 在64位Ubuntu系统上安装32位程序包
在64位Ubuntu系统上安装32位的程序包 $sudo apt-get install package_name:i386 例如: $sudo apt-get install openjdk-7-j ...
- Django项目:堡垒机(Linux服务器主机管理系统)--03--03堡垒机在Linux系统里记录会话日志02/02
#main.py #本文件写所有的连接交互动作程序 # ————————————————03堡垒机在Linux系统里记录会话日志 开始———————————————— from Fortress im ...
- 【课程分享】深入浅出嵌入式linux系统移植开发 (环境搭建、uboot的移植、嵌入式内核的配置与编译)
深入浅出嵌入式linux系统移植开发 (环境搭建.uboot的移植.嵌入式内核的配置与编译) 亲爱的网友,我这里有套课程想和大家分享,假设对这个课程有兴趣的,能够加我的QQ2059055336和我联系 ...
- centos 64位linux系统下安装appt命令
首先,安装apktool包 1. wget http://android-apktool.googlecode.com/files/apktool-install-linux-r04-brut1.ta ...
- 查看linux系统版本是32位还是64位
如何查看ubuntu版本是64位还是32位的: 1.# uname -a 如果有x86_64就是64位的,没有就是32位的 2.# uname -mx86_64 3.# archx86_6 如何查看u ...
- 无光驱在32位windows系统下安装64位windows系统
位的系统. 大家都知道,32位的操作系统最多只能支持3.2G的内存,现在内存白菜价,很多人都在原有基础上购入新内存,这样最少也有4G了,为了让内存不浪费,我 们只有升级到64位操作系统.但是很多朋友又 ...
- 64位Windows系统如何配置32位ODBC数据源
在64位Windows系统中,默认“数据源(ODBC)”是64位的,包括“控制面板->管理工具->数据源 ”或在“运行”中直接运行“ODBCAD32”程序.如果客户端是32位应用程序,仍然 ...
- 非常详细的ok6410的linux系统移植…
目录 Linux 3.3.5系统移植 2 LED驱动移植 8 按键驱动移植 9 LCD驱动移植 11 DM9000网卡驱动移植 14 搭建NFS网络文件系统 25 移植触摸屏驱动 38 移植Qt4.8 ...
随机推荐
- About View
View Geometry Frame & Bounds Graphically, a view can be regarded as a framed canvas. The frame l ...
- Logger.getLogger()和 LogFactory.getLog()
Logger.getLogger()和LogFactory.getLog()的区别: 1.Logger.getLogger()是使用log4j的方式记录日志:2.LogFactory.getLo ...
- html5 placeholder
placeholder是html5<input>标签的一个属性,placeholder 属性提供可描述输入字段预期值的提示信息(hint).该提示会在输入字段为空时显示,并会在字段获得焦点 ...
- (转)面向移动设备的HTML5开发框架
(原)http://www.cnblogs.com/itech/archive/2013/07/27/3220352.html 面向移动设备的HTML5开发框架 转自:http://blogrea ...
- linux命令:ls
1.介绍: ls是linux日常操作中用的最多命令,是list的缩写,默认按名称排序列出当前目录和文件,ls --help可以查看帮助. 2.命令格式: ls [OPTION] [FILE] 3.命令 ...
- Android消息推送
1.引言 所谓的消息推送就是从服务器端向移动终端发送连接,传输一定的信息.比如一些新闻客户端,每隔一段时间收到一条或者多条通知,这就是从服务器端传来的推送消息:还比如常用的一些IM软件如微信.GTal ...
- Windows桌面快捷方式图标全部变成同一个图标的解决方法
今天来个客人,说是电脑的所有程序打开都变成 Adobe Reader 了,打开看了下,刚开始是以为EXE文件关联被修改了,用注册表修复工具弄了下,重启电脑,还是老样子.仔细看了下,原来只是快捷方式变成 ...
- 8、C#基础整理(数组和冒泡排序)
数组 概念:定义一组同类型的指定个数的变量,索引从0开始 例: ];//定义一组有10个数据的数组 shuname[] = ; Console.WriteLine(shuname[]);//打印出1 ...
- BZOJ1576 (最短路+并查集)
#include <cstdio> #include <cstring> #include <algorithm> #include <queue> # ...
- 解决CSS小于12px的文字在谷歌浏览器显示问题
做前端设计的人经常要接触CSS方面的问题,估计有不少人会遇到Chrome谷歌浏览器中CSS设置字体小于12px显示不正常,强创网络在做magento模板过程中就遇到了,起初以为是自己写的CSS的问题, ...