linux 0.12之head.s跳转到main.c的说明

21 篇文章 0 订阅
9 篇文章 0 订阅

因为只有引导代码中使用了as86的编译器,linux0.12其他汇编都是基于GNU as汇编的,这是背景。
head.s是在gnu as下编译的,使用的是AT&T汇编模式。主要完成一些GDT的初始化,然后跳转到main.c函数执行,这两者如何顺利调用的,今天研究一下。
下面是linus给出的关键源码:

after_page_tables:
 pushl $0   # These are the parameters to main :-)
 pushl $0
 pushl $0
 pushl $L6  # return address for main, if it decides to.
 pushl $main
 jmp setup_paging
L6:
 jmp L6  # main should never return here, but
    # just in case, we know what happens.

/*
 * Setup_paging
 *
 * This routine sets up paging by setting the page bit
 * in cr0. The page tables are set up, identity-mapping
 * the first 16MB. The pager assumes that no illegal
 * addresses are produced (ie >4Mb on a 4Mb machine).
 *
 * NOTE! Although all physical memory should be identity
 * mapped by this routine, only the kernel page functions
 * use the >1Mb addresses directly. All "normal" functions
 * use just the lower 1Mb, or the local data space, which
 * will be mapped to some other place - mm keeps track of
 * that.
 *
 * For those with more memory than 16 Mb - tough luck. I've
 * not got it, why should you :-) The source is here. Change
 * it. (Seriously - it shouldn't be too difficult. Mostly
 * change some constants etc. I left it at 16Mb, as my machine
 * even cannot be extended past that (ok, but it was cheap :-)
 * I've tried to show which constants to change by having
 * some kind of marker at them (search for "16Mb"), but I
 * won't guarantee that's all :-( )
 */
.align 2
setup_paging:
 movl $1024*5,%ecx  /* 5 pages - pg_dir+4 page tables */
 xorl %eax,%eax
 xorl %edi,%edi  /* pg_dir is at 0x000 */
 cld;rep;stosl
 movl $pg0+7,pg_dir     /* set present bit/user r/w */
 movl $pg1+7,pg_dir+4   /*  --------- " " --------- */
 movl $pg2+7,pg_dir+8   /*  --------- " " --------- */
 movl $pg3+7,pg_dir+12  /*  --------- " " --------- */
 movl $pg3+4092,%edi
 movl $0xfff007,%eax    /*  16Mb - 4096 + 7 (r/w user,p) */
 std
1:  stosl    /* fill pages backwards - more efficient :-) */
 subl $0x1000,%eax
 jge 1b
 xorl %eax,%eax  /* pg_dir is at 0x0000 */
 movl %eax,%cr3  /* cr3 - page directory start */
 movl %cr0,%eax
 orl $0x80000000,%eax
 movl %eax,%cr0  /* set paging (PG) bit */
 ret     /* this also flushes prefetch-queue */

setup_paging的实现这次不研究,重点在于main函数。
首先看看c代码的调用模式
依旧看一个小c代码的汇编

void swap(int*a ,int *b)
{
 int c;
 c=*a;
 *a=*b;
 *b=c;
}

int main()
{
 int a,b;
 a=1,b=2;
 swap(&a,&b);
 return a+b;
}

在mingw32下gcc -Wall -S -o exch.s exch.c
汇编代码为

 .file  "exch.c"
 .text
 .globl _swap
 .def   _swap;  .scl    2;  .type   32; .endef
_swap:
LFB0:
 .cfi_startproc
 pushl  %ebp
 .cfi_def_cfa_offset 8
 .cfi_offset 5, -8
 movl   %esp, %ebp
 .cfi_def_cfa_register 5
 subl   $16, %esp
 movl   8(%ebp), %eax
 movl   (%eax), %eax
 movl   %eax, -4(%ebp)
 movl   12(%ebp), %eax
 movl   (%eax), %edx
 movl   8(%ebp), %eax
 movl   %edx, (%eax)
 movl   12(%ebp), %eax
 movl   -4(%ebp), %edx
 movl   %edx, (%eax)
 leave
 .cfi_restore 5
 .cfi_def_cfa 4, 4
 ret
 .cfi_endproc
LFE0:
 .def   ___main;    .scl    2;  .type   32; .endef
 .globl _main
 .def   _main;  .scl    2;  .type   32; .endef
_main:
LFB1:
 .cfi_startproc
 pushl  %ebp
 .cfi_def_cfa_offset 8
 .cfi_offset 5, -8
 movl   %esp, %ebp
 .cfi_def_cfa_register 5
 andl   $-16, %esp
 subl   $32, %esp
 call   ___main
 movl   $1, 28(%esp)
 movl   $2, 24(%esp)
 leal   24(%esp), %eax
 movl   %eax, 4(%esp)
 leal   28(%esp), %eax
 movl   %eax, (%esp)
 call   _swap
 movl   28(%esp), %edx
 movl   24(%esp), %eax
 addl   %edx, %eax
 leave
 .cfi_restore 5
 .cfi_def_cfa 4, 4
 ret
 .cfi_endproc
LFE1:

去除链接信息,最简洁的表示为

_swap:
 pushl  %ebp
 movl   %esp, %ebp
 subl   $16, %esp
 movl   8(%ebp), %eax
 movl   (%eax), %eax
 movl   %eax, -4(%ebp)
 movl   12(%ebp), %eax
 movl   (%eax), %edx
 movl   8(%ebp), %eax
 movl   %edx, (%eax)
 movl   12(%ebp), %eax
 movl   -4(%ebp), %edx
 movl   %edx, (%eax)
 leave
 ret
_main:
 pushl  %ebp
 movl   %esp, %ebp
 andl   $-16, %esp
 subl   $32, %esp
 call   ___main
 movl   $1, 28(%esp)
 movl   $2, 24(%esp)
 leal   24(%esp), %eax
 movl   %eax, 4(%esp)
 leal   28(%esp), %eax
 movl   %eax, (%esp)
 call   _swap
 movl   28(%esp), %edx
 movl   24(%esp), %eax
 addl   %edx, %eax
 leave
 ret

看看栈帧是如何利用的。
这里写图片描述
首先理解几个汇编指令
call
把返回地址压入栈中,并跳转到被调函数开始执行。返回地址是紧随call指令后的指令地址。
ret
弹出栈顶的地址,并跳转到该地址执行
leave
恢复ebp和esp,因为在每个被调函数的第一个栈空间是保存的调用者函数的ebp地址,自己的ebp就指向这个地址,所以leave就是将esp跳转回来,并恢复原来ebp
等价于
movl %ebp,%esp
popl %ebp
看到上面示意图是有这样的调用顺序的
main将esp自减32,32的栈空间用于存放数据
将a、b的数据压栈到+28的位置和+24的位置
将参数a、b的地址压栈到如图位置
call指令压入返回地址跳转到swap的栈空间

swap依旧是先将esp-16,开辟一段栈空间
然后保存main的ebp,swap的ebp也就从这个地址开始
movl 8(%ebp), %eax
movl (%eax), %eax
movl %eax, -4(%ebp)
是将&b的内容存到eax中也就是b的地址
然后取地址中的数存放eax,就是b的值,再将b的值压栈。
同理a也一样
执行leave,将esp指向swap栈空间开始的地方,然后恢复main的ebp
ret 此时栈顶回到了main中,就是返回地址的地方,pop出这个地址去执行

明白此,在看看linus是如何完成这个跳转的

setup_paging的最后一个指令就是ret
而setup_paging不是使用的call而是jmp所以就没有自动完成返回地址的压栈,
在jmp前一个指令,有
pushl $main
jmp setup_paging
所以会pop出main的地址然后去执行而mian前面的就是给main传递的参数。
而jmp main前面一个指令是main返回时的跳转地址,这里是一个无限循环,也就是系统会一直run。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值