[置頂] 函數調用、函數傳參與函數返回的實現機制


最近才開始用匯編分析具體過程,之前的一些問題,只到達把參數存儲到棧頂,准備調用函數之類的模糊認知,今天打算再細分析一下。

(不知道csdn還是獵豹瀏覽器爛,排版越搞越爛。)



首先是變量初始化:


這里有一個問題,就是i,j,k在棧中順序倒置了i最先,按理說是高地址(linux棧向下生長)

然后是兩個參數入棧,這個順序沒得說,由右向左入,保證最左邊的參數地址最低,在esp指向的內存。



參數入棧之后就是函數調用了,程序為了觀察,重復調用了兩次,調用就是call 函數入口地址,可以觀察到,固定地址,兩次都是

call 0x8048644<sum(int, int)>

(所以我的虛函數例子中,一個類的各個基類vptr,找到vtable再加偏轉后,那個函數地址應該是固定的?!比如f::func()的入口地址?可以過去驗證一下,給原文補充)


然后跳轉到sum()函數內部了,這是函數代碼段的首地址(可以這么理解吧,具體這個地址范圍,是不是就是text segment,待驗證。),和call指令給的地址一樣。應該這樣理解:所謂函數的入口地址,其實也就是push ebp壓棧指令(因為在第一條)的地址


(frame_dummy不知道干什么的。和哪段代碼銜接。)


下邊是sum()內部的指令:

   x0x8048644 <sum(int, int)>       push   %ebp                     x
x0x8048645 <sum(int, int)+1> mov %esp,%ebp x
x0x8048647 <sum(int, int)+3> sub $0x10,%esp x
x0x804864a <sum(int, int)+6> movl $0x0,-0x4(%ebp) x
x0x8048651 <sum(int, int)+13> mov 0xc(%ebp),%eax x
x0x8048654 <sum(int, int)+16> mov 0x8(%ebp),%edx x
x0x8048657 <sum(int, int)+19> lea (%edx,%eax,1),%eax x
x0x804865a <sum(int, int)+22> mov %eax,-0x4(%ebp) x
0x804865d <sum(int, int)+25> mov -0x4(%ebp),%eax x
x0x8048660 <sum(int, int)+28> leave x
x0x8048661 <sum(int, int)+29> ret


關於調用函數:

call指令之前的esp和ebp,這也就是全局的棧幀一個初始化的狀態:

(gdb) print $esp
$36 = (void *) 0xbffff5f0
(gdb) print $ebp
$37 = (void *) 0xbffff618

下邊開始是<sum(int,int)>的指令:

call之后,push ebp之前,這時候esp等於已經自減4了。因為下邊要用esp給ebp賦值,所以這等於找新棧幀的棧底的操作。

(gdb) print $esp
$42 = (void *) 0xbffff5ec
(gdb) print $ebp
$43 = (void *) 0xbffff618



push之后,esp繼續自減4,ebp還是不變。push就是保存ebp的操作,下一步ebp的內容就被抹了。只不過,這個ebp是存哪去了呢?

(gdb) print $esp
$44 = (void *) 0xbffff5e8
(gdb) print $ebp
$45 = (void *) 0xbffff618

此處esp為何還減4?總共減8了。這個-4應該是給return值預留的一個空間?可以看到,<sum(int,int)>里,$0x0是存到$ebp-4的地址了。對應代碼是c = 0;c的地址就是$ebp-4,逆向增長,c(0xbffff5e4)在ebp與esp之間



mov    %esp,%ebp  之后,ebp被同步,到這,就算是sum()函數的局部棧了?

(gdb) print $esp
$46 = (void *) 0xbffff5e8
(gdb) print $ebp
$47 = (void *) 0xbffff5e8

然后是esp的自減,之前一直疑惑的esp自減的問題,在比較特殊的程序起始部分main()那可能看不太好,在局部的sum()調用這就比較清晰了,

因為要把ebp指到esp處,也就是讓老的棧頂成為新的棧底。此時,兩個指針一樣,想要棧空間,肯定要讓esp再自減,開辟一部分內存出來。

另一個問題也說通了,函數的參數(無論值還是指針),並不屬於當前棧幀,而是上層,在新的ebp之前的位置。

(gdb) print $esp
$48 = (void *) 0xbffff5d8
(gdb) print $ebp
$49 = (void *) 0xbffff5e8


總的內存分布和操作流程,上張圖吧:


2、3、6分別代表esp的變更后的新指向。


不管lea和寄存器的細節了,總之,是把a+b的值寫到c。


最后把臨時變量c的內容3寫到了eax寄存器,准備leave

0x804865d <sum(int, int)+25>    mov    -0x4(%ebp),%eax  

實測,當棧幀從局部函數sum()回到main()下時,eax寄存器的內容不變,也就是說可以用寄存器返回值。至少technically是這樣的,具體它是不是這么用的不知道。

仔細看函數調用返回后的代碼,證明是這樣用的!!!!!

                k = sum(i,j);  

   x0x804869a <main()+56>   call   0x8048644 <sum(int, int)>        x
x0x804869f <main()+61> mov %eax,0x1c(%esp) x
>x0x80486a3 <main()+65> mov 0x18(%esp),%eax
而0x1c(%esp),也就是0xbffff60c,就是k的地址。

所以,函數返回值是借助eax寄存器傳遞的!!!!


函數sum()結尾返回部分:

leave指令執行前的esp和ebp

(gdb) si
(gdb) print $esp
$9 = (void *) 0xbffff5d8
(gdb) print $ebp
$10 = (void *) 0xbffff5e8


leave指令執行后的esp和ebp,都發生了變化,ebp的0xbffff618已經是main的棧底了。esp的0xbffff5ec是之前入棧預留出來的空間,至少不是返回值,不明白。

(gdb) print $esp
$11 = (void *) 0xbffff5ec
(gdb) print $ebp
$12 = (void *) 0xbffff618

 

ret指令執行后的esp和ebp,ebp不變,esp+4,恢復到main時的原始狀態,返回值是借助eax,所以這不可能是返回值了!

(gdb) print $esp
$13 = (void *) 0xbffff5f0
(gdb) print $ebp
$14 = (void *) 0xbffff618

全程打印,局部棧底ebp地址0xbffff5e8數值未改變,應該是沒用上。

比局部棧底高四字節的0xbffff5ec指向的地址(假設是個有意義的地址,十六進制轉換)134514359-》0x80486b7有變化,但是不知道是不是有意義的東西。

總之,上圖空出來的兩個地方的功能不太能解釋。

再去查一下leave和ret的匯編指令。



=========================================================================================================================

還有,return值存哪?

 

 

還有個問題,進入局部棧幀,ebp增長,老的ebp存在哪了?(和之前空出來的八個地址有關?)不存起來肯定是恢復不回去的?但是具體存哪了呢?

 

找來一個參考圖,解釋了這八個地址的作用,就是匯編代碼中沒有顯式的用法:

 

根據此圖,結合本案例

第一句,call造成的esp4,是預留返回地址。

第二句,push ebp是給老ebp留位置。

都是4地址,因為是32位,一個指針都是4bytes

 

call指令之前,esp固定,內容為地址:0xbffff5f0,存放入棧參數i=1;

call指令之后,esp4(圖中就是指向RET返回地址)內容為地址0xbffff5ec,該地址存放:0x804869f其實就是從sum()函數執行結束后的下一條指令的地址。

   x0x804869a <main()+56>   call   0x8048644 <sum(int, int)>        x

   x0x804869f <main()+61>   mov    %eax,0x1c(%esp)  

 

push指令之后,esp4,內容為地址0xbffff5e8,該地址存放:-1073744360,即0xBFFFF618果然是main()函數的基ebp

 

執行leave前,寄存器%ebp存儲局部棧底0xbffff5e8

執行leave的時候,寄存器%ebp恢復到老的ebp地址0xbffff618。(這是入棧時候存好的)

 

執行ret前,寄存器%esp存儲局部棧頂0xbffff5ec

執行ret后,寄存器%esp恢復到老的esp地址0xbffff5f0。(不同於ebp,不知道誰給的

 

參考指令介紹:

RET ; 10)當前堆棧指針2,返回到(3CLR P1.0繼續執行MAIN 主程序。

此處,不是減2是減4.但是果然,新老esp才相差0x4,不是兩次減0x4,又一次自減0x10么,總工需要0x18!

其實%esp也是兩次變更,

在LEAVE操作有一次變更,從0xbffff5d8到0xbffff5ec。指向RET地址了。esp指向的RET比局部EBP高四個地址(一個指針大小),LEAVE的含義可能就在於此吧,已經離開了那個棧幀。

在RET操作還有一次,從0xbffff5ec到0xbffff5f0。這時候程序也跳到RET指定的地址繼續執行了。。。。


不過這么說來,RET算三不管么?也就是個臨時的,不能稱作變量的東西,跳轉地址而已。比main()棧幀的esp還低,比sum()棧幀的ebp還高。

 

最終堆棧調用過程的內存分布:



==================================================================================================================================================================================================================================================

今天補充討論新問題:不同類型的返回怎么實現!

之前基於eax返回函數返回值,就一直在想,為何不能用多個寄存器返回多個值?

一方面,有些語言確實這樣做了,C/C++不做,也許因為語法不方便,畢竟語言很多地方其實是為程序員考慮的,比如左值右值的問題(好像沒什么問題,應該是右值);還有,返回類型代表了函數類型,返回多個值的話函數類型怎么算?

另一方面,就可能是效率!效率,wait!忽略了一個重點,那些個很復雜的東西,它用寄存器返回不了的啊,寄存器就那么大,寄存器效率最高,其實不用寄存器的都有效率問題,區別只是怎么優化這個過程,怎么實現的區別吧!

說到返回多個值呢,大家最常想到的,應該是打包結構體,用容器什么的,或者預先傳個結構體指針進去。

那么具體的實現是什么呢?先以結構體為例:

#include<stdio.h>
typedef struct S{
public:
int i;
int j;
}structS;
structS getStruct()
{
structS s1;
s1.i = 1;
s1.j = 2;
return s1;
}
int main()
{
//structS test = getStruct();//怕算作一種構造,干擾
structS test;
test = getStruct();
printf("i:%d\n",test.i);
printf("j:%d\n",test.j);
}
這里邊試了兩種方式給test賦值,其實本質上,和之前的傳參機制是類似的。

第一種:

structS test = getStruct();

匯編過程:開辟了一塊空間,並把地址傳給主棧幀頂端esp,函數局部就直接在原結構體對象身上開刀了,斷點的話,函數沒返回時,原結構體內容已經被改了。


第二種:

structS test;

test = getStruct();

首先,給test對象開辟了空間,在main()函數棧幀內。然后,把對象地址放到了棧頂esp處。
  >x0x8048467 <getStruct()+3>       mov    0x8(%ebp),%eax           x
   x0x804846a <getStruct()+6>       movl   $0x1,(%eax)              x
   x0x8048470 <getStruct()+12>      movl   $0x2,0x4(%eax)  

函數內部,取出該對象地址到寄存器(體現一個快),然后把i和j的值存入指定地址。本質上,等於一個INOUT的structS*參數傳入。

兩種一模一樣,沒區別,本質都是傳地址,沒走返回值機制,沒用eax,等於走了入參機制,並且函數內部的結構體聲明與定義也沒有占用函數棧幀。這是隱藏傳入指針參數不知道怎么解釋這種事情了。編譯器優化吧!!!!

下面改下代碼,做個對比:

#include<stdio.h>
typedef struct S{
public:
int i;
int j;
}structS;
structS getStruct()
{
structS s1;
s1.i = 1;
s1.j = 2;
return s1;
}
void setStruct(structS *s1)
{
s1->i = 1;
s1->j = 2;
return;
}
int main()
{
structS test;//怕算作一種構造,干擾
setStruct(&test);//怕算作一種構造,干擾
// test = getStruct();
printf("i:%d\n",test.i);
printf("j:%d\n",test.j);

}


過程幾乎一模一樣,唯一的一點小區別,也是非常費解的區別,兩次操作使用的eax寄存器內容明明一樣,可以省去第二次0x8(%ebp),%eax的,之前的那種做法就省去了。所以,也可能是語句不同,編譯器優化不一樣了?



現在,關於函數返回值結構體的實現機制就清晰了。


更多其他類型的話:

char也是用eax返回

double就不一樣了。

   x0x8048474 <getValue()>          push   %ebp                     x
x0x8048475 <getValue()+1> mov %esp,%ebp x
x0x8048477 <getValue()+3> sub $0x10,%esp x
x0x804847a <getValue()+6> fldl -0x8(%ebp) x
x0x804847d <getValue()+9> leave x
>x0x804847e <getValue()+10> ret
浮點數指令這看不太好,不確定!
沒有顯式的寄存器保存,內存也是ebp負向偏移,局部的。


long long,32位下,寄存器只有32位,一個long long是用兩個寄存器eax和edx一起返回的。

函數內

   x0x804849c <getLongLong()+20>    mov    -0x8(%ebp),%eax          x
x0x804849f <getLongLong()+23> mov -0x4(%ebp),%edx x
函數外,返回值給變量賦值

  >x0x80484bb <main()+23>   mov    %eax,0x18(%esp)                  x
x0x80484bf <main()+27> mov %edx,0x1c(%esp) x


向下兼容,char和short都像int一樣,用一個eax寄存器存儲返回值。




總之,基本上就兩種返回方法,壓棧傳參傳地址法和寄存器暫存返回法。最終保證局部棧幀結束后,變量能夠保存下來。


=========================================================================================================================

補充(20160315):有了上邊的基礎,來看一個破壞性試驗:

兩層函數func1()和sum()。在sum函數內部強行更改壓棧的ebp地址(c地址+8偏移所儲存內容即是)

#include<iostream>
using namespace std;

int sum(int a,int b){
int c = 0;
c = a + b;
int *p = &c;
p++;
p++;
*p = *p - 0x10;
return c;
}
int func1(int a, int b)
{
int c = sum(a,b);
return c;
}
int main(){
int iiiii = 0;
int i = 1;
int j = 2;
int k = 0;
k = func1(i,j);
// k = sum(i,j);

cout << k << endl;
cout << i << endl;
cout << j << endl;
return 0;
}

流程分析:

初始ebp 0xbffff618,

變量iiiii地址0xbffff600,i地址604j地址608

調用func1(),壓棧后,當前ebp 0xbffff5e8(內容0xbffff618),

調用sum(),再次壓棧,當前ebp 0xbffff5c8(內容0xbffff5e8),

在sum()函數體內指針p強指向ebp0xbffff5c8指向的內容0xbffff5e8,-0x10,即為0xbffff5d8,

從sum()返回,出棧后ebp破壞,返回值需要靠ebp 的值來找,原來的0xbffff5e8 - 0x4變成了0xbffff5d8 - 0x4 == 0xbffff5d4,返回值存儲到了錯誤的位置。

從func1()返回,k沒有得到應有的返回值,仍然為野值:

(gdb) print $ebp
$19 = (void *) 0xbffff5d8
(gdb) print *(0xbffff5d4)
$20 = 3

(gdb) print &k
$22 = (int *) 0xbffff60c

更遠的后果,其實沒有,因為返回靠的是ret地址,改ebp只改一層,也就是從sum()函數改亂了func1()函數的棧幀基址,回到main()函數就一切正常了。

所以此例的影響主要就是值返回失敗。


重新理一下位置關系:ebp寄存器的地址指向老ebp壓棧的位置,地址+4為ret地址,地址-4為局部變量c地址,因為語句是return c;所以需要c存儲值。


萬惡的指針!!!!嘿嘿!

指針不封裝,不封裝就有權限可以亂改,可一旦改錯,后果也是要多嚴重有多嚴重。


PS:改了return地址就真的亂了,不過下邊也可以試試,應該還可以達成另類goto語句,自己實現循環之類的。

=========================================================================================================================

下邊寫個例子:

#include<iostream>
using namespace std;

int func1(int a, int b)
{
int c = a + b;
int *p = &c;
p++;
p++;
p++;
*p = *p - 0x19;
return c;
}
int main(){
int iiiii = 0;
int i = 1;
int j = 2;
int k = 0;
i++;
k = func1(i,j);

cout << k << endl;
cout << i << endl;
cout << j << endl;
return 0;
}
猜猜這個運行結果?

死循環!!

自減0x19是把func1()的ret地址從本該返回的位置改成i++的位置,這樣每次出來跳轉到i++

但是這個死循環的前提是“干凈”!!如果中間有系統調用如cout和printf()之類的,則不能達成,例如這種:

        i++;
cout << "i:"<< i << endl;
k = func1(i,j);
和這種:

int func1(int a, int b)
{
cout << "a:"<< a << endl;
int c = a + b;

因為又有函數跳轉了嘛。各種錯誤吧,可能結束,甚至可能core dumped。還有就是語句多了以后,語句差值也不同了, 減法的數值是要根據情況改的!

加上打印,算好合適的跳轉距離,重新設計:

#include<iostream>
using namespace std;

int func1(int a, int b)
{
int c = a + b;
int *p = &c;
p++;
p++;
p++;
*p = *p - 0x4d;
return c;
}
int main(){
int iiiii = 0;
int i = 1;
int j = 2;
int k = 0;
i++;
cout << "i:"<< i << endl;
k = func1(i,j);

cout << k << endl;
cout << i << endl;
cout << j << endl;
return 0;
}



這就是我想要的效果

運行結果:


最后,如果你不知道這個跳轉值怎么來的,復制我上邊的例子,打開

#gdb target

打開目標可執行文件

(gdb)b 15

打個斷點

(gdb)run

運行一下卡住,然后layout asm看就行了。


0x750-0x703等於0x4d。





=========================================================================================================================

下邊是之前思考順帶留下的疑問:不影響分析入棧出棧過程,博主有空再來鞏固,現在不能分心搞這種“副業”。



=========================================================================================================================

附1:好吧,這是個強迫症問題,不影響學習機理,看別的夠用了,先掛起了。。。。

網友推薦參考書籍(不確定有答案):《老碼仕途》 《代碼揭秘》


sum()起始部分這些東西和main開始有些相似,都是push ebp,但又不太一樣

main()的初始化:

   x0x8048662 <main()>              push   %ebp                     x
x0x8048663 <main()+1> mov %esp,%ebp x
x0x8048665 <main()+3> and $0xfffffff0,%esp x
x0x8048668 <main()+6> sub $0x20,%esp
這個意思應該是設立程序自己的棧區,ebp和esp初始一致,,然后根據變量數量和調用函數需要的壓棧空間,以0x10為單位開辟一定倍數大小的空間,esp指下去,ebp和esp之間就是當前的stack frame棧幀了。

疑問,這個0xfffffff0賦值給esp之后ebp就沒有操作了,另外,那個也不是賦值,是一個與操作。就是esp和ebp本來一樣,進行一個與操作,等於像頁(塊)一樣的取個整?

這樣之后還要再減那么多是什么意思?取整的過程中esp會和ebp產生怎樣的差別?


main()的壓棧和sum()的壓棧到底有何不同?因為sum()的壓棧只是一個小遷移,而main()的壓棧是整個程序的棧的開辟過程?按現在的理解是這樣。

假設,在進main之前有個特殊的esp地址,因為是編譯過程產生的邏輯地址,所以按理說是多少都可以,這個沒有一個規則固定產生么?

總之,在一個選定的esp地址下,先把esp同步到ebp,此時兩個指針指向一樣的地址,做完與操作之后,等於esp會清空了16個以內的地址(32位,一個十六進制數歸0),然后自減一個合適的棧空間大小。最大的疑惑就在這假如說這一過程還倒退了怎么辦?為何一定要這樣操作,為了地址達到某種對齊?固然,減去0x20一定能夠彌補0xfffffff0與操作,但是不是比較浪費么,就是說只需要0x10個地址存數據,卻需要進行一個-0x20操作。

不是這個意思,理解反了,因為是逆向增長,一位清零也是在增加空間,自減20也是在增加空間。所以實際是讓esp比ebp多了20多個地址。

(但是一定這么巧,ebp都是8字底?試了好幾次編譯。)

(gdb) print $esp
$1 = (void *) 0xbffff5f0
(gdb) print $ebp
$2 = (void *) 0xbffff618


總之,我還是傾向那個對齊理論吧,畢竟是所有棧的初始化!但是因為這動的是esp,ebp還是有“個位數”的。其次,局部的棧,esp和ebp也沒有就因為main初始的取整操作而變整。

$3 = (void *) 0xbffff5e8
(gdb) print $ebp
$4 = (void *) 0xbffff5e8


=========================================================================================================================






=========================================================================================================================

附2:下面的東西說不太通,先存起來。

ip寄存器(32位eip),ip寄存器的內容很多時候(還是永久?)是和pc寄存器內容一樣的!是下一條指令的地址。

按理說是pc存的是下一條指令地址,ip是從內存中找到的當前指令內容,不知道為什么兩者相同。可能單步調試剛好卡在某個節點上?但是為何相等。

組成原理課本是說的兩種東西,

但網友說“pc就是ip,cs存放段基址,ip存放偏移量,沒有單獨的pc的說法。”

也有網友說有單獨的pc的。

“詳情參考王爽匯編語言第三版”

gdb也給出了$pc和$eip兩種訪問方法。



(圖中沒打印pc寄存器,但值是和eip一樣的)

(gdb) print $eip
$32 = (void (*)(void)) 0x8048644 <sum(int, int)>
(gdb) print $pc
$33 = (void (*)(void)) 0x8048644 <sum(int, int)>
(gdb) si
(gdb) print $pc
$34 = (void (*)(void)) 0x8048645 <sum(int, int)+1>
(gdb) print $eip
$35 = (void (*)(void)) 0x8048645 <sum(int, int)+1>




=========================================================================================================================

下邊是之前思考順帶留下的疑問:不影響分析入棧出棧過程,博主有空再來鞏固,現在不能分心搞這種“副業”。

=========================================================================================================================

附1:細心可觀察內存分布

最高的是棧,從高往低增長(linux)
然后是靜態區,存一些全局變量

最下邊是只讀區,尤以代碼地址最低,其次是一些常量字符串“xxxxxx”之類的。


=========================================================================================================================

附2:好吧,這是個強迫症問題,不影響學習機理,看別的夠用了,先掛起了。。。。

網友推薦參考書籍(不確定有答案):《老碼仕途》 《代碼揭秘》


sum()起始部分這些東西和main開始有些相似,都是push ebp,但又不太一樣

main()的初始化:

   x0x8048662 <main()>              push   %ebp                     x
x0x8048663 <main()+1> mov %esp,%ebp x
x0x8048665 <main()+3> and $0xfffffff0,%esp x
x0x8048668 <main()+6> sub $0x20,%esp
這個意思應該是設立程序自己的棧區,ebp和esp初始一致,,然后根據變量數量和調用函數需要的壓棧空間,以0x10為單位開辟一定倍數大小的空間,esp指下去,ebp和esp之間就是當前的stack frame棧幀了。

疑問,這個0xfffffff0賦值給esp之后ebp就沒有操作了,另外,那個也不是賦值,是一個與操作。就是esp和ebp本來一樣,進行一個與操作,等於像頁(塊)一樣的取個整?

這樣之后還要再減那么多是什么意思?取整的過程中esp會和ebp產生怎樣的差別?


main()的壓棧和sum()的壓棧到底有何不同?因為sum()的壓棧只是一個小遷移,而main()的壓棧是整個程序的棧的開辟過程?按現在的理解是這樣。

假設,在進main之前有個特殊的esp地址,因為是編譯過程產生的邏輯地址,所以按理說是多少都可以,這個沒有一個規則固定產生么?

總之,在一個選定的esp地址下,先把esp同步到ebp,此時兩個指針指向一樣的地址,做完與操作之后,等於esp會清空了16個以內的地址(32位,一個十六進制數歸0),然后自減一個合適的棧空間大小。最大的疑惑就在這假如說這一過程還倒退了怎么辦?為何一定要這樣操作,為了地址達到某種對齊?固然,減去0x20一定能夠彌補0xfffffff0與操作,但是不是比較浪費么,就是說只需要0x10個地址存數據,卻需要進行一個-0x20操作。

不是這個意思,理解反了,因為是逆向增長,一位清零也是在增加空間,自減20也是在增加空間。所以實際是讓esp比ebp多了20多個地址。

(但是一定這么巧,ebp都是8字底?試了好幾次編譯。)

(gdb) print $esp
$1 = (void *) 0xbffff5f0
(gdb) print $ebp
$2 = (void *) 0xbffff618


總之,我還是傾向那個對齊理論吧,畢竟是所有棧的初始化!但是因為這動的是esp,ebp還是有“個位數”的。其次,局部的棧,esp和ebp也沒有就因為main初始的取整操作而變整。

$3 = (void *) 0xbffff5e8
(gdb) print $ebp
$4 = (void *) 0xbffff5e8


=========================================================================================================================






=========================================================================================================================

附3:下面的東西說不太通,先存起來。

ip寄存器(32位eip),ip寄存器的內容很多時候(還是永久?)是和pc寄存器內容一樣的!是下一條指令的地址。

按理說是pc存的是下一條指令地址,ip是從內存中找到的當前指令內容,不知道為什么兩者相同。可能單步調試剛好卡在某個節點上?但是為何相等。

組成原理課本是說的兩種東西,

但網友說“pc就是ip,cs存放段基址,ip存放偏移量,沒有單獨的pc的說法。”

也有網友說有單獨的pc的。

“詳情參考王爽匯編語言第三版”

gdb也給出了$pc和$eip兩種訪問方法。



(圖中沒打印pc寄存器,但值是和eip一樣的)

(gdb) print $eip
$32 = (void (*)(void)) 0x8048644 <sum(int, int)>
(gdb) print $pc
$33 = (void (*)(void)) 0x8048644 <sum(int, int)>
(gdb) si
(gdb) print $pc
$34 = (void (*)(void)) 0x8048645 <sum(int, int)+1>
(gdb) print $eip
$35 = (void (*)(void)) 0x8048645 <sum(int, int)+1>










注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com