C++ 中對 main 函數的地址賦值會怎樣?
考慮如下代碼:
int main(){ *(int*)main = 0; // or whatever}這樣寫會發生什麼,是未定義行為嗎?
以下引用均來自 n4700
首先,不能在你的程序中使用 main 函數
6.6.1 (3)
The function main shall not be used within a program.
其次,標準不保證一定可以將函數指針轉換到對象指針,void*也是
8.2.10 (8) Converting a function pointer to an object pointer type or vice versa is conditionally-supported.
再其次,函數是不可修改的
6.10 (7) An lvalue is modifiable unless its type is const-qualified or is a function type.
再再其次,強制通過不符合要求的類型來訪問其他類型也是ub
6.10 (8) If a program attempts to access the stored value of an object through a glvalue of other than one of the following types the behavior is undefined:
(8.1) the dynamic type of the object,(8.2) a cv-qualified version of the dynamic type of the object,
(8.3) a type similar (as defined in 7.5) to the dynamic type of the object,(8.4) a type that is the signed or unsigned type corresponding to the dynamic type of the object,(8.5) a type that is the signed or unsigned type corresponding to a cv-qualified version of the dynamic type of the object,(8.6) an aggregate or union type that includes one of the aforementioned types among its elements or non-static data members (including, recursively, an element or non-static data member of a sub aggregate or contained union),(8.7) a type that is a (possibly cv-qualified) base class type of the dynamic type of the object,(8.8) a char,unsigned char, or std::byte type.
題主可以數數違反了多少條【
是UB
好像有編譯器選項讓進入點變成別的函數這樣說不定就可以達到沒有關係的狀況
不過這是一個奇怪的手段
嵌入式設備還沒有完善系統時會用到的樣子限定工具和平台的話,行為就會變成確定的(逃
一般來說代碼段是只讀的,試圖往裡面寫東西程序就直接崩了
按題目中這麼寫。
行為挺確定的, 試圖寫程序段。
或者更準確地說試圖寫入沒有寫許可權地址,比如該頁不可寫。
在類 Unix 系統上發出個錯誤信號,比如 SIGSTOP。
然後程序就 dump 掉了。
--------------------------- 分割線
注意,為避免誤導,以下代碼不是標準,它只是小心翼翼地生成了特定目標代碼。編譯器還是 linux 64-bit / gcc
如果你只是想對 main 賦值,看會發生什麼。
那麼不用定義 main 函數了,直接賦值,比如
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
編譯成功並輸出 hello world。Linux 系統。
也許這篇文章可以幫到你
Main is usually a function. So then when is it not?
題主自己跑一下不就知道了?Ps:個人覺得,你寫代碼的地址已經被執行掉了(除非你一次寫大量,追上已經執行部分),所以什麼都不會發生。
code段不能直接賦值
你這個代碼是個UB。
不過,你這個代碼讓我想到了以前木馬、外掛常常使用的HOOK技術。實際上通過函數地址直接寫入數據的代碼是真的存在。但這種技術不屬於C和C++的範疇了。
舉個簡單的例子,在WINDOWS+x86平台下
比如有一個函數
void hello() {
printf("hello
");
}
那麼我們通過一段代碼把hello函數體第一條指令改成RET(機器碼是C3)
*(uint8_t*)hello = 0xc3;
然後去調用hello的時候就直接返回了……
更常見的是把原函數第一條指令改成JMP指令,然後跳轉到自己的函數里進行某些操作,最後再恢復原函數的頭部,重新調用原函數。
還有一點要注意的是,CODE段是不能直接寫入數據的,在WINDOWS下好像有某個函數可以解除這個限制,記不清了……
發生段錯誤
Start
Segmentation fault
Finish
禁止修鍊C++黑魔法。學語言的目的是解決問題,不是讓你變成語法律師。
程序已停止工作
可以inline hook實現代碼注入(逃
------------------看沒什麼人說這個,那我就扯一下吧。。。-----------------------
的確,這是UB,但如 @vczh 所說,在特定平台下就不是了,那來看看在windows32位的平台下,會(gai)怎(zen)么(me)樣(wan)。
#include &
#include &
#include &
#define JMP_OPCODE 0xe9
#define JMP_OPCODE_LEN 1
#define SIZE_OF_JMP 5
int main();
int hookmain();
class HookTheMain
{
public:
HookTheMain()
{//全局對象的構造函數會於main之前被調用
DWORD oldProtect;
VirtualProtect(main, SIZE_OF_JMP, PAGE_EXECUTE_READWRITE, oldProtect);
*(BYTE*)main = JMP_OPCODE;
*(DWORD*)((BYTE*)main + JMP_OPCODE_LEN) =
((BYTE*)hookmain - (BYTE*)main - SIZE_OF_JMP);
VirtualProtect(main, SIZE_OF_JMP, oldProtect, oldProtect);
//隨手寫的,比較丑。。。也沒做什麼錯誤判斷。。。
}
};
HookTheMain h;
int main()
{
printf("This code will not be executed
");
system("pause");
return 0;
}
int hookmain()
{
printf("This code will be executed instead
");
system("pause");
return 0;
}
這裡就要說下彙編語言了,jmp是一條跳轉指令,以0xE8打頭,後面跟一個小端的32位偏移,會跳轉到 jmp指令的地址+偏移數值+jmp指令的大小也就是5 的位置。
如果把main函數的前5個位元組寫成跳轉到另一個函數的jmp,代碼裡面我通過(BYTE*)hookmain - (BYTE*)main - SIZE_OF_JMP算出hookmain函數的偏移,這樣程序執行到main的時候就會跳轉,而不是執行之前的main。
調用構造函數前的main
調用構造函數後的main
會跳轉到這裡,即hookmain
因為代碼是不可寫的(像題主那樣直接寫會爆寫入異常),所以要先VirtualProtect(注意這不是C的標準,是windows的API),再寫。
還有就是C++全局類的構造函數執行會在main之前。。不然執行到main再改變那5個位元組就沒什麼用了。。。
在32位windows以上代碼運行結果如下:
可見,hookmain被調用了,而不是main
寫這個的目的是,給題主科普,這個除了是UB以外,還可以這麼玩,當然也歡迎題主進入二進位安全的大坑(霧
推薦閱讀:
※關於2048局面的價值判斷及ai思路?
※為什麼5%的CPU佔用會造成這麼大的性能損失?
※為什麼工控還在用c?
※C++中左值、右值與寄存器的關係是怎樣的?
※LOL盒子這類的輔助工具一般都是用什麼開發的?