GNU编译器“.previous“的解释

linux源代码中汇编语言部分总是有.previous、.section、.long,这是一个ELF段堆栈操作命令。其他的段堆栈操作命令还有.section、.subsection、.pushsection、.popsection,本命令交换当前段(及其子段)和最近访问过的段(及其子段)。多个连续的.previous命令将使当前位置两个段(及其子段)之间反复切换。用段堆栈的术语来说,本命令使当前段和堆顶段交换位置。

.previous表示恢复到当前.section定义之前的那个段作为当前段。也就是说这个previous与它前面的那个section一般成对出现.

只不过是一段汇编,主要还是对目标地址进行读写,不过在代码中添加了修正代码(.fixup),其目的就是当发生异常时执行该修正代码,防止内核被BUG掉。

1. __copy_user

宏__copy_user在include/asm-i386/uaccess.h中定义,是作为从用户空间和内核空间进行内存复制的关键。这个宏扩展为汇编后如下:

000 #define __copy_user(to,from,size)
001 do {
002 int __d0, __d1;
003 __asm__ __volatile__(
004 "0: rep; movsl\n"
005 " movl %3,%0\n"
006 "1: rep; movsb\n"
007 "2:\n"
008 ".section .fixup,\"ax\"\n"
009 "3: lea 0(%3,%0,4),%0\n"
010 " jmp 2b\n"
011 ".previous\n"
012 ".section __ex_table,\"a\"\n"
013 " .align 4\n"
014 " .long 0b,3b\n"
015 " .long 1b,2b\n"
016 ".previous"
017 : "=&c"(size), "=&D" (__d0), "=&S" (__d1)
018 : "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)
019 : "memory");
020 } while (0)

这段代码的主要操作就是004-007行,它的主要功能是将from处长度为size的数据复制到to处。
看这段代码之前,先看看它的约束条件:

017 : "=&c"(size), "=&D" (__d0), "=&S" (__d1)
018 : "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)
019 : "memory");

017是输出部,根据描述可知size保存在ecx中,__d0保存在DI中,__d1保存在SI中。
018是输入部,根据描述可知size/4(即size除以4后的整数部分)保存在ecx中,size&3(即size除以4的余数部分)随便保存在某一个寄存器中,to保存在DI中,from保存在SI中。
然后再反过头来看004-007行,就明白了:
004行:将size/4个4字节从from复制到to。为了提高速度,这里使用的是movsl,所以对size也要处理一下。
005行:将size&3,即size/4后余下的余数,复制到ecx中。
006行:根据ecx中的数量,从from复制数据到to,这里使用的是movsb。
007行:代码结束。

到这里,复制就结束了。
但是实际上没有这么简单,因为还可能发生复制不成功的现象,所以008-016行的代码都是进行此类处理的。
内核提供了一个意外表,它的每一项的结构是(x,y),即如果在地址x上发生了错误,那么就跳转到地址y处,这里行012-015就是利用了这个机制在编译时声明了两个表项。将这几行代码说明如下:

012行:声明以下内容属于段__ex_table。
013行:声明此处内容4字节对齐。
014行:声明第一个意外表项,即如果在标志0处出错,就跳转到标志3处(.section .fixup段中)。
015行:声明第二个意外表项,即如果在标志1处出错,就跳转到标志2处(.section .text段中)。

上面之所以要在标志后面加上b,是因为引用之前的代码,如果要引用之后的代码就加f。
这里对size的操作约定是:如果复制失败,则size中保留的是还没有复制完的数据字节数。
由于复制数据的代码只有4行,其中可能出现问题的就是004和006行。从上面的异常表可以看出,内核的处理策略是:

(1) 如果在0处出错,那么这时没有复制完的字节数就是ecx中剩余的数字乘以4加上先前size除以4以后的那个余数。009行代码即完成此任务,“lea 0(%3,%0,4),%0”即计算“%ecx = (size % 4) + %ecx * 4”,并将这个数值赋值给返回C代码的size中。

(2)如果在1处出现错误,那么由于之前ecx中的size/4个字节都已经复制成功了,所以只需要将保存在任意一个寄存器中的size/4的余数赋值给size返回。

从汇编代码中可以看到,009行的异常处理代码被编译到一个叫做fixup的段中。
可见这段代码的本质就是从from复制数据到to,并对两处可能出现错误的地方进行简单的异常处理——返回未复制的字节数。

注意:

(1).section .fixup,"ax";.section __ex_table,"a";

将这两个.section和.previous中间的代码汇编到各自定义的段中,然后跳回去,将这之后的的代码汇编到.text段中,也就是自定义段之前的段。.section和.previous必须配套使用。

(2)例子中__ex_table异常表的安排在用户空间是不会得到执行的,它只在内核中有效。

自己写了个测试下:

文件sramboot.S 

#include"regdef.h"
 .set noreorder
 .text
 .globl start
 start:
     .set noat
     la at,value
     lw a1, 4(at)
     nop
     add a2, a0, a1
     sw a2, 8(at)

 .data
 .section soochow ,"a"
 .align 2
 value:
     .long 0x0001
 .section seu,"a"
     .long 0x0004
 .previous
     .long 0x0002
     .long 0x0003
 .section seu,"a"
     .long 0x0005

Makefile文件

CROSS_COMPILE=mipsellinux-
CC = $(CROSS_COMPILE)gcc
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
OBJDUMP = $(CROSS_COMPILE)objdump
OBJCOPY = $(CROSS_COMPILE)objcopy
TEXTBASE =# -Ttext
DATABASE =
LDFLAGS = $(TEXTBASE) $(DATABASE) -T my.ld

test.bin:test.exe
     $(OBJCOPY) -O binary test.exe test.bin

test.exe: sramboot.o
     $(LD)? sramboot.o $(LDFLAGS) -o test.exe
     $(OBJDUMP) -D test.exe > test.dump
 
sramboot.o:sramboot.S
     $(CC) -E sramboot.S -o sramboot.s
     $(AS) sramboot.s -o sramboot.o
     @rm sramboot.s
 
clean:
     @rm *.o *.exe *.bin *.dump

链接脚本my.ld

OUTPUT_FORMAT("elf32-tradlittlemips","elf32-tradbigmips","elf32-tradlittlemips")
OUTPUT_ARCH(mips)
ENTRY(start)
SECTIONS
{
    . =0;
    .text :
    {
        *(.text)
    }
    .data :
    {
        *(.data)
    }
    .bss :
    {
        *(.bss)
    }
}

bin文件反汇编脚本test.sh

#!/bin/sh
A=0
if [ ! -e test.bin ]
then
        make
        A=1
fi

mipsel-linux-objdump -D -m mips -b binary -EL -M no-aliases -z test.bin >my.dump

if [ $A = 1 ]
    then
        rm sramboot.o *bin *exe
fi

结果:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值