linux汇编 __copy_user宏(zz)

 


该文档主要是将以下两篇文档的内容copy到了一起:
(1)"利用异常表处理 Linux 内核态缺页异常"
(2)"对用户/内核空间数据复制的实现和自己的理解"
 

     用户进程的地址映射有的时候还没建立(如堆栈),在pg_dir里页没相应的设置。如果在用户态对这个地址进行访问,就会产生页面异常,在异常处理函数中 会根据实际情况(如检查VM_GROWSDOWN等标志)对页面重新建立映射。而在内核进程访问这些地址时,如果不用get_user等函数的话,一旦发 生缺页异常整个内核就会BUG掉。
我曾经在内核态下对用户进程的地址空间直接进行读写,结果就panic了,只有用get_user才行。
      只不过是一段汇编,主要还是对目标地址进行读写,不过在代码中添加了修正代码(.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异常表的安排在用户空间是不会得到执行的,它只在内核中有效。
(3) 将.fixup段和.text段独立开来的目的是为了提高CPU流水线的利用率。熟悉体系结构的读者应该知道,当前的CPU引入了流水线技术来加快指令的 执行,即在执行当前指令的同时,要将下面的一条甚至多条指令预取到流水线中。这种技术在面对程序执行分支的时候遇到了问题:如果预取的指令并不是程序下一 步要执行的分支,那么流水线中的所有指令都要被排空,这对系统的性能会产生一定的影响。在我们的这个程序中,如果将.fixup段的指令安排在正常执行 的.text段中,当程序执行到前面的指令时,这几条很少执行的指令会被预取到流水线中,正常的执行必然会引起流水线的排空操作,这显然会降低整个系统的 性能。
 
2.在用户程序中使用__copy_user
/* hello.c */
#include <stdio.h>
#include <string.h>
#define __copy_user(to,from,size)    /
do {        /
 int __d0, __d1;      /
 __asm__ __volatile__(     /
  "0: rep; movsl/n"    /
  " movl %3,%0/n"    /
  "1: rep; movsb/n"    /
  "2:/n"      /
  ".section .fixup,/"ax/"/n"   /
  "3: lea 0(%3,%0,4),%0/n"   /
  " jmp 2b/n"    /
  ".previous/n"     /
  ".section __ex_table,/"a/"/n"   /
  " .align 4/n"           /
  " .long 0b,3b/n"    /
  " .long 1b,2b/n"    /
  ".previous"      /
  : "=&c"(size), "=&D" (__d0), "=&S" (__d1)  /
  : "r"(size & 3), "0"(size / 4), "1"(to), "2"(from) /
  : "memory");      /
} while (0)
int main(void)
{
  const char *string = "Hello, world!";
  char buf[20];
  unsigned long n, m;
  m = n = strlen(string);
  __copy_user(buf, string, n);
  buf[m] = '/0';
  printf("%s/n", buf);
  exit(0);
}
$gcc hello.c -o hello
$objdump --disassemble --section=.text hello
hello:     file format elf32-i386
Disassembly of section .text:
8048498: 8b 45 c4              mov      0xffffffc4(%ebp),%eax
804849b: 83 e0 03              and       $0x3,%eax
804849e: 8b 55 c4              mov      0xffffffc4(%ebp),%edx
80484a1: 89 d1                mov      %edx,%ecx
80484a3: c1 e9 02              shr       $0x2,%ecx
80484a6: 8d 7d c8              lea       0xffffffc8(%ebp),%edi
80484a9: 8b 75 f4              mov      0xfffffff4(%ebp),%esi
80484ac: f3 a5                repz movsl  %ds:(%esi),%es:(%edi)
80484ae: 89 c1                mov      %eax,%ecx
80484b0: f3 a4                repz movsb  %ds:(%esi),%es:(%edi)
80484b2: 89 c8                mov      %ecx,%eax
$objdump --disassemble --section=.fixup hello
hello:     file format elf32-i386
Disassembly of section .fixup:
08048530 <.fixup>:
8048530: 8d 4c 88 00           lea    0x0(%eax,%ecx,4),%ecx
8048534: e9 79 ff ff ff        jmp    80484b2 <main+0x42>
$objdump --full-contents --section=__ex_table hello
hello:     file format elf32-i386
Contents of section __ex_table:
8048578 ac840408 30850408 b0840408 b2840408  ....0...........
由于x86使用小尾端的编址方式,上面的这段数据比较凌乱。把上面的__ex_table中的内容转变成大家通常看到的样子:
8048578 80484ac 8048530 80484b0 80484b2  ....0...........
(1).long 0b,3b对应80484ac 8048530,其中80484ac对应的指令为.text中的repz movsl %ds:(%esi),%es:(%edi),8048530对应的指令为.fixup中的lea  0x0(%eax,%ecx,4),%ecx。
(2).long 1b,2b对应80484b0 80484b2,其中80484b0对应的指令为.text中的repz movsb %ds:(%esi),%es:(%edi),80484b2对应的指令为.text中的mov %ecx,%eax。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值