[英]Translating O2 optimized for-loop from assembly to C
這是一個作業問題。 我正在嘗試從以下匯編代碼(x86 linux計算機,使用gcc -O2優化編譯)中獲取信息。 我評論了每個部分以顯示我所知道的。 我的大部分假設可能是錯誤的,但是我已經做了足夠的搜索,直到我知道我應該在這里提出這些問題。
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "result %lx\n" //Printed string at end of program
.text
main:
.LFB13:
xorl %esi, %esi // value of esi = 0; x
movl $1, %ecx // value of ecx = 1; result
xorl %edx, %edx // value of edx = 0; Loop increment variable (possibly mask?)
.L2:
movq %rcx, %rax // value of rax = 1; ?
addl $1, %edx // value of edx = 1; Increment loop by one;
salq $3, %rcx // value of rcx = 8; Shift left rcx;
andl $3735928559, %eax // value of eax = 1; Value AND 1 = 1;
orq %rax, %rsi // value of rsi = 1; 1 OR 0 = 1;
cmpl $22, %edx // edx != 22
jne .L2 // if true, go back to .L2 (loop again)
movl $.LC0, %edi // Point to string
xorl %eax, %eax // value of eax = 0;
jmp printf // print
.LFE13: ret // return
我應該將其轉換為以下C代碼,並填入空格
#include <stdio.h>
int main()
{
long x = 0x________;
long result = ______;
long mask;
for (mask = _________; mask _______; mask = ________) {
result |= ________;
}
printf("result %lx\n",result);
}
我有兩個問題和健全性檢查,我想確保自己做對了,因為我發現的所有類似示例都不是針對優化代碼的。 自己編寫一些試驗后,我得到的結果很接近,但是L2的中間部分始終關閉。
我的理解
首先,將esi與自身進行異或,得到0,由x表示。 然后將1添加到ecx中,這將由變量result表示。
x = 0; result = 1;
然后,我相信循環增量變量存儲在edx中並設置為0。這將在for循環的第三部分(更新表達式)中使用。 我也認為此變量必須是mask,因為稍后在edx上添加1,表示循環增量(mask = mask ++),並且在for循環的中間部分對edx進行了比較(測試表達式又稱為mask!= 22)。 )。
mask = 0; (in a way)
然后進入循環,將rax設置為1。由於我沒有聲明第四個變量,所以我根本不知道在哪里使用了它,盡管稍后會顯示它要被ANDED和清零。
movq %rcx, %rax;
然后將循環變量加一
addl $1, %edx;
下一部分讓我感覺到最少
我覺得接下來的三個操作組成了循環的主體表達,但是我不知道該如何處理它們。 它會導致類似於| = x ...的結果,但我不知道還有什么
salq $3, %rcx
andl $3735928559, %eax
orq %rax, %rsi
其余的我覺得我掌握的很好。 進行比較(如果mask!= 22,再次循環),並打印結果。
我遇到的問題我不了解幾件事。
1)我不明白如何弄清楚我的變量。 程序集中似乎有3個硬編碼的變量以及一個增量或臨時存儲變量(rax,rcx,rdx,rsi)。 我認為rsi將是x ,而rcx將是result ,但是我不確定mask是rdx還是rax,無論哪種方式,最后一個變量是什么?
2)我不確定的3種表達方式有什么作用? 我覺得我以某種方式將它們與增量混合在一起,但是在不知道變量的情況下,我不知道如何解決這個問題。
任何和所有的幫助將是巨大的,謝謝!
答案是 :
#include <stdio.h>
int main()
{
long x = 0xDEADBEEF;
long result = 0;
long mask;
for (mask = 1; mask != 0; mask = mask << 3) {
result |= mask & x;
}
printf("result %lx\n",result);
}
在組裝中:
rsi
是result
。 我們推論這是因為它是唯一獲得OR
ed的值,並且它是printf
的第二個參數(在x64 linux中,參數按順序存儲在rdi
, rsi
, rdx
和其他一些參數中)。
x
是設置為0xDEADBEEF
的常量。 這是不確定的,但確實有道理,因為它似乎在C代碼中設置為常量,並且此后似乎沒有設置。
現在,剩下的內容將被GCC的反優化功能所迷惑。 您會發現,GCC檢測到該循環將被准確執行21次,並且認為巧妙地處理該條件並將其替換為無用的計數器很明智。 知道這一點,我們看到edx
是無用的計數器,而rcx
是mask
。 然后,我們可以推斷出實際條件和實際的“增量”操作。 我們可以在程序集中看到<<= 3
,並注意,如果將64位int左移22次,則變為0(將3移22意味着將66位移位,因此全部移出了)。
不幸的是,這種反優化對於GCC來說確實很常見。 該組件可以替換為:
.LFB13:
xorl %esi, %esi
movl $1, %ecx
.L2:
movq %rcx, %rax
andl $3735928559, %eax
orq %rax, %rsi
salq $3, %rcx // implicit test for 0
jne .L2
movl $.LC0, %edi
xorl %eax, %eax
jmp printf
它的作用完全相同,但是我們刪除了無用的計數器並保存了3條匯編指令。 它還與C代碼更好地匹配。
讓我們往后一點。 我們知道result
必須是printf()
的第二個參數。 在x86_64調用約定中,這是%rsi
。 循環是.L2
標簽和jne .L2
指令之間的所有內容。 我們在模板中看到循環的末尾有一個result |=
行,並且確實有一個以%rsi
為目標的orl
指令,因此可以檢出。 現在,我們可以在.main
的頂部看到它的初始化.main
。
ElderBug是正確的,因為編譯器通過添加計數器來偽優化。 但是我們仍然可以弄清楚:當循環重復時,哪條指令在|=
之后立即運行? 那一定是循環的第三部分。 循環主體之前會運行什么? 那一定是循環初始化。 不幸的是,您必須弄清楚在原始循環的第22次迭代中會發生什么,才能對循環條件進行逆向工程。 (但是sal
是左移,而該行是原始循環條件的痕跡,在插入%rdx
測試之前,該條件條件分支之后將是一個條件分支。)
請注意,在%rax
%rcx
進行修改之前,代碼在%rcx
保留了mask
值的副本,並且x
被折疊為一個常數(仔細看一下andl
行)。
另請注意,您可以將.S文件輸入gas
以獲取.o文件,然后查看其作用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.