欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

匯編實(shí)現(xiàn)的memcpy和memset的方法

 更新時(shí)間:2020年02月09日 11:28:37   作者:掃帚的影子 ·  
這篇文章主要介紹了匯編實(shí)現(xiàn)的memcpy和memset的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

天天山珍海味的吃,也會(huì)煩。偶爾來(lái)點(diǎn)花生,毛豆小酌一點(diǎn),也別有一番風(fēng)味。

天天java, golang, c++, 咱們今天來(lái)點(diǎn)匯編調(diào)劑一下,如何?

通過(guò)這篇文章,您可以了解過(guò):

  • CPU寄存器的一些知識(shí);
  • 函數(shù)調(diào)用的過(guò)程;
  • 匯編的一些知識(shí);
  • glibc 中 memcpy和memset的使用;
  • 匯編中memcpy和memset是如何實(shí)現(xiàn)的;

閑話不多說(shuō),今天來(lái)看看匯編中如何實(shí)現(xiàn)memcpymemset(腦子里快回憶下你最后一次接觸匯編是什么時(shí)候......)

函數(shù)是如何被調(diào)用的

棧的簡(jiǎn)單介紹

  • 棧對(duì)函數(shù)調(diào)用來(lái)說(shuō)特別重要,它其實(shí)就是進(jìn)程虛擬地址空間中的一部分,當(dāng)然每個(gè)線程可以設(shè)置單獨(dú)的調(diào)用棧(可以用戶指定,也可以系統(tǒng)自動(dòng)分配); 棧由?;?%ebp)和棧頂指針(%esp)組成,這兩個(gè)元素組成一個(gè)棧幀,棧一般由高地址向低地址增長(zhǎng),將數(shù)據(jù)壓棧時(shí)%esp減小,反之增大;
  • 調(diào)用一個(gè)新函數(shù)時(shí),會(huì)產(chǎn)生一個(gè)新的棧幀,即將老的%ebp壓棧,然后將%ebp設(shè)置成跟當(dāng)前的%esp一樣的值即可。函數(shù)返回后,之前壓棧的數(shù)據(jù)依然出棧,這樣最終之前進(jìn)棧的%ebp也會(huì)出棧,即調(diào)用函數(shù)之前的棧幀被恢復(fù)了,也正是這種機(jī)制支撐了函數(shù)的多層嵌套調(diào)用;

不管是寫(xiě)Windows程序還是Linux程序,也不管是用什么語(yǔ)言來(lái)寫(xiě)程序,我們經(jīng)常會(huì)把某個(gè)獨(dú)立的功能抽出來(lái)封裝成一個(gè)函數(shù),然后在需要的地方調(diào)用即可??此坪?jiǎn)單的用法,那它背后是如何實(shí)現(xiàn)的呢?一般分為四步:

函數(shù)調(diào)用規(guī)則

  • 函數(shù)一般都會(huì)有多個(gè)參數(shù),我們根據(jù)函數(shù)調(diào)用時(shí),
  • 參數(shù)壓棧的方向(參數(shù)從左到右入棧,還是從右到左入棧);函數(shù)調(diào)用完是函數(shù)調(diào)用者負(fù)責(zé)將之前入棧的參數(shù)退棧,還是被調(diào)用函數(shù)本身來(lái)作等

這兩點(diǎn)(其實(shí)還有一點(diǎn),就是代碼被編譯后,生成新函數(shù)名的規(guī)則,跟我們這里介紹的關(guān)系不大)來(lái)分類函數(shù)的調(diào)用方式:

  • stdcall: 函數(shù)參數(shù)由右向左入棧, 函數(shù)調(diào)用結(jié)束后由被調(diào)用函數(shù)清除棧內(nèi)數(shù)據(jù);
  • cdecl: 函數(shù)參數(shù)由右向左入棧, 函數(shù)調(diào)用結(jié)束后由函數(shù)調(diào)用者清除棧內(nèi)數(shù)據(jù);
  • fastcall: 從左開(kāi)始不大于4字節(jié)的參數(shù)放入CPU的EAX,ECX,EDX寄存器,其余參數(shù)從右向左入棧, 函數(shù)調(diào)用結(jié)束后由被調(diào)用函數(shù)清除棧內(nèi)數(shù)據(jù);

這種方式最大的不同是用寄存器來(lái)存參數(shù),所有它fast。

glibc中的memcpy

我們先來(lái)看下glibc中的memcpy , 原型如下:

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

從src拷貝連續(xù)的n個(gè)字節(jié)數(shù)據(jù)到dest中, 不會(huì)有任何的內(nèi)存越界檢查。

char dest[5] = {0};                                                  
char test[5] = {0,'b'};                                                
char src[10] = {'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'};      
 ::memcpy(dest, src, 6);   
                          
std::cout << src << std::endl; 
std::cout << dest << std::endl;                                          
std::cout << test << std::endl;

大家有興趣的話可以考慮下上面的代碼輸出是什么?

匯編實(shí)現(xiàn)的memcpy

說(shuō)來(lái)慚愧,匯編代碼作者本人也不會(huì)寫(xiě)。不過(guò)我們可以參考linux源碼里面的實(shí)現(xiàn),這相對(duì)還是比較權(quán)威的吧。

它的實(shí)現(xiàn)位于arch/x86/boot/copy.S, 文件開(kāi)頭有這么一行注釋Copyright (C) 1991, 1992 Linus Torvalds, 看起來(lái)應(yīng)該是大神親手寫(xiě)下的。我們來(lái)看一看

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)

CPU的眾多通用寄存器有%esi和%edi, 它們一個(gè)是源址寄存器,一個(gè)是目的寄存器,常被用來(lái)作串操作,我們的這個(gè)memcpy最終就是將%esi指向的內(nèi)容拷貝到%edi中,因?yàn)檫@種代碼在linux源碼中是被標(biāo)識(shí)成了.code16, 所有這里都只用到這兩個(gè)寄存器的低16位:%si和%di;

代碼的第一,二句保存當(dāng)前的%si和%di到棧中;

這段代碼實(shí)際上是fastcall調(diào)用方式,void *memcpy(void *dest, const void *src, size_t n);

其中 dest 被放在了%ax寄存器,src被放在了%dx, n被放在了%cx;

movw %ax, %di, 將dest放入%di中,movw %dx, %s,將stc放入%si中;

一個(gè)字節(jié)一個(gè)字節(jié)的拷貝太慢了,我們四個(gè)字節(jié)四個(gè)字節(jié)的來(lái),shrw $2, %cx,看看參數(shù)n里面有幾個(gè)4, 我們就需要循環(huán)拷貝幾次,循環(huán)的次數(shù)存在%cx中,因?yàn)楹竺孢€要用到這個(gè)%cx, 所以計(jì)算之前先將其壓棧保存pushw %cx;

rep; movslrep重復(fù)執(zhí)行movsl這個(gè)操作,每執(zhí)行一次%cx的內(nèi)容就減一,直到為0。movsl每次從%si中拷貝4個(gè)字節(jié)到%di中。這其實(shí)就相當(dāng)于一個(gè)for循環(huán)copy;

參數(shù)n不一定能被4整除,剩下的余數(shù),我們只能一個(gè)字節(jié)一個(gè)字節(jié)的copy了。

andw $3, %cx就是對(duì)%cx取余,看還剩下多少字節(jié)沒(méi)copy;

rep; movsb一個(gè)字節(jié)一個(gè)字節(jié)的copy剩下的內(nèi)容;

glibc中的memset

我們先來(lái)看下glibc中的memset, 原型如下:

void *memset(void *s, int c, size_t n);

這個(gè)函數(shù)的作用是用第二個(gè)參數(shù)的最低位一個(gè)字節(jié)來(lái)填充s地址開(kāi)始的n個(gè)字節(jié),盡管第二個(gè)參數(shù)是個(gè)int, 但是填充時(shí)只會(huì)用到它最低位的一個(gè)字節(jié)。

你可以試一下下面代碼的輸出:

int c = 0x44332211;                                                  
int s = 0;                                                     
::memset((void*)&s, c, sizeof(s));                                           
std::cout << std::setbase(16) << s << std::endl; // 11111111

匯編實(shí)現(xiàn)的memset

我們還是來(lái)看一下arch/x86/boot/copy.S中的實(shí)現(xiàn):

GLOBAL(memset)
  pushw  %di
  movw  %ax, %di
  movzbl %dl, %eax
  imull  $0x01010101,%eax
  pushw  %cx
  shrw  $2, %cx
  rep; stosl
  popw  %cx
  andw  $3, %cx
  rep; stosb
  popw  %di
  retl
ENDPROC(memset)

不同于memcpy,這里不需要%si源址寄存器,只需要目的寄存器,所以我們先將其壓棧保存pushw %di;

參考void *memset(void *s, int c, size_t n)可知,參數(shù)s被放在了%ax寄存器;參數(shù)n被放在了%cx寄存器;

參數(shù)c被放在了%dl寄存器,這里只用到了%edx寄存器的最低一個(gè)字節(jié),所以對(duì)于c這個(gè)參數(shù)不管你是幾個(gè)字節(jié),其實(shí)多只有最低一個(gè)字節(jié)被用到;

memcpy一樣,一次一個(gè)字節(jié)的操作太慢了,一次四個(gè)字節(jié)吧,假設(shè)參數(shù)c的最低一個(gè)字節(jié)是0x11, 那么一次set四個(gè)字節(jié)的話,就是0x11111111:

movzbl %dl, %eaximull $0x01010101,%eax

imull $0x01010101,%eax這句話就是把0x11變成0x11111111

rep; stosl,rep重復(fù)執(zhí)行stosl 這個(gè)操作,每執(zhí)行一次%cx的內(nèi)容就減一,直到為0。stosl每次從%eax中拷貝4個(gè)字節(jié)到%di中。這其實(shí)就相當(dāng)于一個(gè)for循環(huán)copy;

參數(shù)n不一定能被4整除,剩下的余數(shù),我們只能一個(gè)字節(jié)一個(gè)字節(jié)的copy了。

andw $3, %cx就是對(duì)%cx取余,看還剩下多少字節(jié)沒(méi)copy;

rep; stosl 一個(gè)字節(jié)一個(gè)字節(jié)的copy剩下的內(nèi)容;

總結(jié)

以上所述是小編給大家介紹的匯編實(shí)現(xiàn)的memcpy和memset的方法,希望對(duì)大家有幫助!

相關(guān)文章

  • 匯編語(yǔ)言功能字符串大小寫(xiě)轉(zhuǎn)換實(shí)現(xiàn)實(shí)例詳解

    匯編語(yǔ)言功能字符串大小寫(xiě)轉(zhuǎn)換實(shí)現(xiàn)實(shí)例詳解

    這篇文章主要為大家介紹了匯編語(yǔ)言功能大小寫(xiě)轉(zhuǎn)換實(shí)現(xiàn)的實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2021-11-11
  • 匯編語(yǔ)言DOSBox及debug.exe在Windows64下環(huán)境搭建

    匯編語(yǔ)言DOSBox及debug.exe在Windows64下環(huán)境搭建

    這篇文章主要為大家介紹了匯編語(yǔ)言環(huán)境的搭建DOSBox及debug.exe在Windows64下安裝配置過(guò)程,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • 匯編指令:JO、JNO、JB..的使用方法

    匯編指令:JO、JNO、JB..的使用方法

    這篇文章主要介紹了匯編指令:JO、JNO、JB..的使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • 匯編實(shí)現(xiàn)的memcpy和memset的方法

    匯編實(shí)現(xiàn)的memcpy和memset的方法

    這篇文章主要介紹了匯編實(shí)現(xiàn)的memcpy和memset的方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-02-02
  • 匯編語(yǔ)言Debug命令詳解教程

    匯編語(yǔ)言Debug命令詳解教程

    這篇文章主要為大家介紹了匯編語(yǔ)言Debug命令的教程,文中對(duì)Debug的命令進(jìn)行了全面的講解有需要的朋友可以借鑒參考共同學(xué)習(xí)下,希望能夠有所幫助
    2021-11-11
  • 匯編語(yǔ)言lea指令使用方法解析

    匯編語(yǔ)言lea指令使用方法解析

    這篇文章主要介紹了匯編語(yǔ)言lea指令使用方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • 匯編語(yǔ)言中的segment

    匯編語(yǔ)言中的segment

    segment是段的意思,是段定義偽指令,一個(gè)正常的應(yīng)用程序被由若干個(gè) segment組成,接下來(lái)通過(guò)本文給大家介紹匯編語(yǔ)言中的segment,需要的朋友可以參考下
    2020-01-01
  • GNU ARM匯編語(yǔ)法原理及操作解析

    GNU ARM匯編語(yǔ)法原理及操作解析

    這篇文章主要介紹了GNU ARM匯編語(yǔ)法原理及操作解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • Go 中的循環(huán)是如何轉(zhuǎn)為匯編的(方法詳解)

    Go 中的循環(huán)是如何轉(zhuǎn)為匯編的(方法詳解)

    這篇文章主要介紹了Go 中的循環(huán)是如何轉(zhuǎn)為匯編的,本文通過(guò)循環(huán)的匯編代碼給大家講解的非常詳細(xì),代碼簡(jiǎn)單易懂,非常不錯(cuò),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-05-05
  • 匯編語(yǔ)言?masm5與debug命令使用方法小結(jié)

    匯編語(yǔ)言?masm5與debug命令使用方法小結(jié)

    本文總結(jié)了匯編程序的基本框架及規(guī)范、masm5工具的基本使用方法、debug命令的基本使用方法
    2023-08-08

最新評(píng)論