【操作系统】Linux Kernel中memcpy的汇编实现 详解(包括必要基础概念等)

memcpy汇编实现

由于这篇博客是从我的各种笔记上搬的

我不知道自己为什么看了mmcpy汇编实现,也许单纯就是因为好玩

在arch/x86/boot/Copy.S中,由Linus在1992年写的一段代码

GLOBAL(memcpy)
    pushw   %si
    pushw   %di
    movw    %ax, %di
    movw    %dx, %si
    pushw   %cx
    shrw    $2, %cx
    rep; movsl
    popw    %cx
    andw    $3, %cx
    rep; movsb
    popw    %di
    popw    %si
    retl
ENDPROC(memcpy)

必要的基础概念

要弄懂上面这一段代码,必须弄清楚一下内容

1.寄存器命名

rax eax ax 说的都是一个寄存器,只是使用的位不同。

img

2.函数调用约定

在CPU中,计算机*没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数*。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递

在参数传递中,有两个很重要的问题必须得到明确说明:

  • 当参数个数多于一个时,按照什么顺序把参数压入堆栈
  • 函数调用后,由谁来把堆栈恢复原装

在高级语言中,通过函数调用约定来说明这两个问题。

常见调用约定:

stdcall cdecl fastcall thiscall naked call

  • stdcall: 函数参数由右向左入栈, 函数调用结束后由被调用函数清除栈内数据;
  • cdecl: 函数参数由右向左入栈, 函数调用结束后由函数调用者清除栈内数据;
  • fastcall: 从左开始不大于4字节的参数放入CPU的EAX,ECX,EDX寄存器,其余参数从右向左入栈, 函数调用结束后由被调用函数清除栈内数据; 这种方式最大的不同是用寄存器来存参数,所有它fast。

这里用的是fastcall,但是一二三个参数分别在%ax %dx %cx

原因一:

代码在linux源码中是被标识成了.code16, 所有这里都只用到这两个寄存器的低16位

原因二:

[逆向工程入门-函数调用-fastcall-GCC reparam](https://wizardforcel.gitbooks.io/re-for-beginners/content/Part-VI/Chapter-64.html#64.3.1 GCC regparm)

3.串操作

CPU的众多通用寄存器有**%esi和%edi**, 它们一个是源址寄存器,一个是目的寄存器,常被用来作串操作

4.几条汇编指令

—mov %ax , %dx 将 %ax放入%dx

—rep; movsl, rep重复执行movsl这个操作 rep执行次数由%ecx控制,%ecx非空则一直执行(%cx)

—movsl 这就是所谓串操作 movsl 是 传送双字(注意,因为只用到了寄存器低16位,所以一个字是16bit,注意要区分跟字节的关系,所以双字是32bit,四个字节),movsl操作的是%si 和 %di 从si拷贝到di在传送完成之后,SI和DI(或者ESI和EDI)会增加或者减小。详解看这里

—andw $3, %cx 按位与运算 就是对%cx取余,结果仍放在%cx(所以需要提前压栈,保存cx之前的值—pushw %cx)位运算奇技淫巧看这里

–shrw $2, %cx 就是逻辑右移运算,相当于%cx / (2^N) N=2,结果仍然放在了%cx

现在我们就可以正式来分析这段代码了

void *memcpy(void *dest, const void *src, size_t n);

其中 dest 被放在了%ax寄存器,src被放在了%dx, n被放在了%cx;,其中**%cx存放的是 字节数**

    pushw   %si
    pushw   %di
    movw    %ax, %di
    movw    %dx, %si

我们要用到%si(source)和%di(destination)寄存器进行串操作,所以先保存原来的值,然后将*dest地址给到di,src给到si

    //pushw   %si
    //pushw   %di
    //movw    %ax, %di
    //movw    %dx, %si
    pushw   %cx
    shrw    $2, %cx
    rep; movsl

然后我们想要传输的效率高一点,一个一个字节拷贝太慢了,所以我们决定四个字节四个字节的拷贝(movsl)

通过shrw逻辑右移运算。也就是给cx(别忘了,cx存放的是memcpy的字节数)除以4(2^N N=2)并保存到%cx,也就是说,四个四个字节拷贝,我能拷多少次,然后通过rep;movsl进行拷贝(次数由%cx控制)

   // pushw   %si
   // pushw   %di
   // movw    %ax, %di
   // movw    %dx, %si
   // pushw   %cx
    //shrw    $2, %cx
    //rep; movsl
    popw    %cx
    andw    $3, %cx
    rep; movsb
    popw    %di
    popw    %si
    retl

四个四个字节拷贝结束后,考虑一下,这个size不一定永远能被4整除,所以我们要取余,把最后的碎片拷贝过去。先pop出来原来的size大小%cx,然后取余(按位与3,即0011),将结果保存在了%cx然后一个字节一个字节的拷贝。

ok,整个过程结束了,把压栈的di,si,pop出来,一切恢复原样,此栈帧结束了。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LiujiaHuan13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值