📜 [專欄新文章] Reason Why You Should Use EIP1167 Proxy Contract. (With Tutorial)
✍️ Ping Chen
📥 歡迎投稿: https://medium.com/taipei-ethereum-meetup #徵技術分享文 #使用心得 #教學文 #medium
EIP1167 minimal proxy contract is a standardized, gas-efficient way to deploy a bunch of contract clones from a factory.
1. Who may consider using EIP1167
For some DApp that are creating clones of a contract for its users, a “factory pattern” is usually introduced. Users simply interact with the factory to get a copy. For example, Gnosis Multisig Wallet has a factory. So, instead of copy-and-paste the source code to Remix, compile, key in some parameters, and deploy it by yourself, you can just ask the factory to create a wallet for you since the contract code has already been on-chain.
The problem is: we need standalone contract instances for each user, but then we’ll have many copies of the same bytecode on the blockchain, which seems redundant. Take multisig wallet as an example, different multisig wallet instances have separate addresses to receive assets and store the wallet’s owners’ addresses, but they can share the same program logic by referring to the same library. We call them ‘proxy contracts’.
One of the most famous proxy contract users is Uniswap. It also has a factory pattern to create exchanges for each ERC20 tokens. Different from Gnosis Multisig, Uniswap only has one exchange instance that contains full bytecode as the program logic, and the remainders are all proxies. So, when you go to Etherscan to check out the code, you’ll see a short bytecode, which is unlikely an implementation of an exchange.
0x3660006000376110006000366000732157a7894439191e520825fe9399ab8655e0f7085af41558576110006000f3
What it does is blindly relay every incoming transaction to the reference contract 0x2157a7894439191e520825fe9399ab8655e0f708by delegatecall.
Every proxy is a 100% replica of that contract but serving for different tokens.
The length of the creation code of Uniswap exchange implementation is 12468 bytes. A proxy contract, however, has only 46 bytes, which is much more gas efficient. So, if your DApp is in a scenario of creating copies of a contract, no matter for each user, each token, or what else, you may consider using proxy contracts to save gas.
2. Why use EIP1167
According to the proposal, EIP is a “minimal proxy contract”. It is currently the known shortest(in bytecode) and lowest gas consumption overhead implementation of proxy contract. Though most ERCs are protocols or interfaces, EIP1167 is the “best practice” of a proxy contract. It uses some EVM black magic to optimize performance.
EIP1167 not only minimizes length, but it is also literally a “minimal” proxy that does nothing but proxying. It minimizes trust. Unlike other upgradable proxy contracts that rely on the honesty of their administrator (who can change the implementation), address in EIP1167 is hardcoded in bytecode and remain unchangeable.
That brings convenience to the community.
Etherscan automatically displays code for EIP1167 proxies.
When you see an EIP1167 proxy, you can definitely regard it as the contract that it points to. For instance, if Etherscan finds a contract meets the format of EIP1167, and the reference implementation’s code has been published, it will automatically use that code for the proxy contract. Unfortunately, non-standard EIP1167 proxies like Uniswap will not benefit from this kind of network effect.
3. How to upgrade a contract to EIP1167 compatible
*Please read all the steps before use, otherwise there might have problems.
A. Build a clone factory
For Vyper, there’s a function create_with_code_of(address)that creates a proxy and returns its address. For Solidity, you may find a reference implementation here.
function createClone(address target) internal returns (address result){ bytes20 targetBytes = bytes20(target); assembly { let clone := mload(0x40) mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000) mstore(add(clone, 0x14), targetBytes) mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000) result := create(0, clone, 0x37) }}
You can either deploy the implementation contract first or deploy it with the factory’s constructor. I’ll suggest the former, so you can optimize it with higher runs.
contract WalletFactory is CloneFactory { address Template = "0xc0ffee"; function createWallet() external returns (address newWallet) { newWallet = createClone(Template); }}
B. Replace constructor with initializer
When it comes to a contract, there are two kinds of code: creation code and runtime code. Runtime code is the actual business logic stored in the contract’s code slot. Creation code, on the other hand, is runtime code plus an initialization process. When you compile a solidity source code, the output bytecode you get is creation code. And the permanent bytecode you can find on the blockchain is runtime code.
For EIP1167 proxies, we say it ‘clones’ a contract. It actually clones a contract’s runtime code. But if the contract that it is cloning has a constructor, the clone is not 100% precise. So, we need to slightly modify our implementation contract. Replace the constructor with an ‘initializer’, which is part of the permanent code but can only be called once.
// constructorconstructor(address _owner) external { owner = _owner;}// initializerfunction set(address _owner) external { require(owner == address(0)); owner = _owner;}
Mind that initializer is not a constructor, so theoretically it can be called multiple times. You need to maintain the edge case by yourself. Take the code above as an example, when the contract is initialized, the owner must never be set to 0, or anyone can modify it.
C. Don’t assign value outside a function
As mentioned, a creation code contains runtime code and initialization process. A so-called “initialization process” is not only a constructor but also all the variable assignments outside a function. If an EIP1167 proxy points to a contract that assigns value outside a function, it will again have different behavior. We need to remove them.
There are two approaches to solve this problem. The first one is to turn all the variables that need to be assigned to constant. By doing so, they are no longer a variable written in the contract’s storage, but a constant value that hardcoded everywhere it is used.
bytes32 public constant symbol = "4441490000000000000000000000000000000000000000000000000000000000";uint256 public constant decimals = 18;
Second, if you really want to assign a non-constant variable while initializing, then just add it to the initializer.
mapping(address => bool) public isOwner;uint public dailyWithdrawLimit;uint public signaturesRequired;
function set(address[] _owner, uint limit, uint required) external { require(dailyWithdrawLimit == 0 && signaturesRequired == 0); dailyWithdrawLimit = limit; signaturesRequired = required; //DO SOMETHING ELSE}
Our ultimate goal is to eliminate the difference between runtime code and creation code, so EIP1167 proxy can 100% imitate its implementation.
D. Put them all together
A proxy contract pattern splits the deployment process into two. But the factory can combine two steps into one, so users won’t feel different.
contract multisigWallet { //wallet interfaces function set(address[] owners, uint required, uint limit) external;}contract walletFactory is cloneFactory { address constant template = "0xdeadbeef"; function create(address[] owners, uint required, uint limit) external returns (address) { address wallet = createClone(template); multisigWallet(wallet).set(owners, required, limit); return wallet; }}
Since both the factory and the clone/proxy has exactly the same interface, no modification is required for all the existing DApp, webpage, and tools, just enjoy the benefit of proxy contracts!
4. Drawbacks
Though proxy contract can lower the storage fee of deploying multiple clones, it will slightly increase the gas cost of each operation in the future due to the usage of delegatecall. So, if the contract is not so long(in bytes), and you expect it’ll be called millions of times, it’ll eventually be more efficient to not use EIP1167 proxies.
In addition, proxy pattern also introduces a different attack vector to the system. For EIP1167 proxies, trust is minimized since the address they point to is hardcoded in bytecode. But, if the reference contract is not permanent, some problems may happen.
You might ever hear of parity multisig wallet hack. There are multiple proxies(not EIP1167) that refer to the same implementation. However, the wallet has a self-destruct function, which empties both the storage and the code of a contract. Unfortunately, there was a bug in Parity wallet’s access control and someone accidentally gained the ownership of the original implementation. That did not directly steal assets from other parity wallets, but then the hacker deleted the original implementation, making all the remaining wallets a shell without functionality, and lock assets in it forever.
https://cointelegraph.com/news/parity-multisig-wallet-hacked-or-how-come
Conclusion
In brief, the proxy factory pattern helps you to deploy a bunch of contract clones with a considerably lower gas cost. EIP1167 defines a bytecode format standard for minimal proxy and it is supported by Etherscan.
To upgrade a contract to EIP1167 compatible, you have to remove both constructor and variable assignment outside a function. So that runtime code will contain all business logic that proxies may need.
Here’s a use case of EIP1167 proxy contract: create adapters for ERC1155 tokens to support ERC20 interface.
pelith/erc-1155-adapter
References
https://eips.ethereum.org/EIPS/eip-1167
https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/
Donation:
pingchen.eth
0xc1F9BB72216E5ecDc97e248F65E14df1fE46600a
Reason Why You Should Use EIP1167 Proxy Contract. (With Tutorial) was originally published in Taipei Ethereum Meetup on Medium, where people are continuing the conversation by highlighting and responding to this story.
👏 歡迎轉載分享鼓掌
同時也有10000部Youtube影片,追蹤數超過2,910的網紅コバにゃんチャンネル,也在其Youtube影片中提到,...
「assembly code教學」的推薦目錄:
- 關於assembly code教學 在 Taipei Ethereum Meetup Facebook 的最讚貼文
- 關於assembly code教學 在 紀老師程式教學網 Facebook 的精選貼文
- 關於assembly code教學 在 紀老師程式教學網 Facebook 的最讚貼文
- 關於assembly code教學 在 コバにゃんチャンネル Youtube 的最讚貼文
- 關於assembly code教學 在 大象中醫 Youtube 的精選貼文
- 關於assembly code教學 在 大象中醫 Youtube 的最讚貼文
- 關於assembly code教學 在 [心得] 個人的x86 組合語言觀念筆記- 看板ASM - 批踢踢實業坊 的評價
- 關於assembly code教學 在 紀老師程式教學網- [免費影音教學] x86 組合語言簡介(Intro to ... 的評價
- 關於assembly code教學 在 紀老師程式教學網- [免費影音教學] x86 組合語言簡介(Intro to ... 的評價
- 關於assembly code教學 在 [教學資源] 71 部關於組合語言(Assembly)與C++ 的影音教學 的評價
- 關於assembly code教學 在 x86組合語言指令集 - CHUPCCA 的評價
- 關於assembly code教學 在 組合語言教學的推薦與評價,PTT和紀老師程式教學網這樣回答 的評價
assembly code教學 在 紀老師程式教學網 Facebook 的精選貼文
[免費影音教學] x86 組合語言簡介(Intro to x86 Assembly Language)
YouTube 播放清單: https://goo.gl/qwA6ah
「組合語言」是目前最低階的程式語言之一。對於整天得跟底層硬體「交戰」的朋友,它是個又愛又恨的程式語言。「愛」的是它能做到對硬體很細微的操作控制,「恨」的是它不太好學。
今天跟大家介紹的,是一位國外熱心網友拍攝的。他針對 x86 組合語言,鉅細靡遺地解釋其中的奧妙。如果您想學 x86 組合語言,但又不想看厚重的教科書,或許這類 YouTube 播放清單可以考慮看看。
原文雖然是英文發音,不過大家可以把英文字幕打開,並隨時按暫停觀看字幕。這樣,就算聽力不是很好的網友,也應該可以多少聽懂作者想表達什麼。
喜歡的朋友,還請按讚鼓勵喔!也可以轉發給您需要這則教學的朋友。如果對於組合語言有什麼想問的,也可以留言在下方!
今天是星期五了,大家加油!馬上就是美麗的週末了!祝福大家週末愉快!
assembly code教學 在 紀老師程式教學網 Facebook 的最讚貼文
[海量影片] 想體驗一下被免費教學影片淹沒的快感嗎?「Derek Banas」這個 YouTube 影音教學頻道 600+ 支影片滿足你!
https://goo.gl/yhCPvl
#eLearning #FreeTrainingVideo
好的!我第一次看到這個 YouTube 影音教學頻道,噴出口的第一句就是:「你 XX 的太閒了啊!」(稱讚意味,默默收藏)
這位 Derek 老兄不知道是繼承了億萬遺產,還是因為興趣使然才猛力做教學影片的?您點進去看,從程式語言、網頁設計、電子電路、資料庫、手機 App、軟體工程、電腦繪圖...甚至於低卡飲食、如何減重、銷售技巧、行銷手法...每樣都有教學影片!您若進去看過他的頻道播放清單,您就會同意我說的話(記得點擊最下方那個「載入更多影片」按鈕,才能完整欣賞到該位老兄的豐功偉業)。
總而言之(丸尾推眼鏡)~~ 喜歡免費教學影片的人有福了!建議您直接訂閱該頻道,或者將我提供的網址加入最愛,然後每天找個三、五片來虐自己...呃...是進修自己一下,包準您程式設計進步的幅度比別人大喔!
咳咳~只丟一個連結就放生可愛的粉絲們,並非我的作風。所以我把他「與程式設計有關」的影片,分門別類地幫大家整理在下面。如果您只對某個領域有興趣,那就看我提供的清單吧!我還幫你把學習順序大致排好了喔!看到我這麼努力還不趕快把「讚」按下去!... XD (無誤)
一:快速學習系列(總計:x39)
> Learn in One Video (x39) - https://goo.gl/fnDxlo
==> 這個系列設計成「學一種語言最多不超過 90 分鐘」,適合業務、PM,或只想懂皮毛就好的人看。
二:網頁程式設計(總計:x219)
[前端網頁設計](小計:x73)
> HTML Tutorial (x4) - https://goo.gl/pXT82h
> HTML Video Tutorial (x2) - https://goo.gl/KXZwtD
> CSS Tutorial (x7) - https://goo.gl/BaUdhp
> CSS Video Tutorial (x7) - https://goo.gl/O8DN8b
> JavaScript Tutorial (x8) - https://goo.gl/wpW0Vi
> JavaScript Video Tutorial (x11) - https://goo.gl/LONxOO
> CoffeeScript Tutorial (x2) - https://goo.gl/ADIvW6
> JQuery Video Tutorial (x9) - https://goo.gl/zRYIlv
> AngularJS Tutorial (x4) - https://goo.gl/WXfguL
> XML Tutorial (x4) - https://goo.gl/LAaFmd
> XML Video Tutorial (x7) - https://goo.gl/WH4Ajo
> AJAX Tutorial (x8) - https://goo.gl/nVItM9
[後端網頁設計](小計:x59)
> PHP Tutorial (x12) - https://goo.gl/7RIa3s
> PHP Video Tutorial (x24) - https://goo.gl/UBdzty
> PHP Security (x6) - https://goo.gl/WjQonR
> NodeJS Tutorial (x12) - https://goo.gl/fzVKZf
> Ruby on Rails Tutorial (x5) - https://goo.gl/dFh0AQ
[網頁程式設計其它](小計:x11)
> 介面設計 How to Layout a Website (x5) - https://goo.gl/9YInAR
> Web Services Tutorial (x6) - https://goo.gl/nr7Yj9
[無痛架站](小計:x76)
> Wordpress Tutorial (x16) - https://goo.gl/X3qNyA
> Wordpress Theme Tutorial (x60) - https://goo.gl/Ac9V4C
三:程式語言(總計:x156)
[一般程式語言](小計:x149)
> C Video Tutorial (x16) - https://goo.gl/wQgJVK
> Java Video Tutorial (x95) - https://goo.gl/TcVfGp
> Java Algorithms (x17) - https://goo.gl/uAQzcC
> Python Tutorial (x9) - https://goo.gl/2c0Ynj
> Python 2.7 Tutorial (x18) - https://goo.gl/yTcJSF
> 組合語言 Assembly Language (x4) - https://goo.gl/PXYlyx
[程式語言相關技巧](小計:x7)
> 正規表示式 Regex Tutorial (x7) - https://goo.gl/x81Khr
四:資料庫(總計 x14)
[SQL](小計:x10)
> SQL Tutorial (x6) - https://goo.gl/YClykN
> SQLite 3 Tutorial (x4) - https://goo.gl/8yHyLP
[NoSQL](小計:x4)
> MongoDB Tutorial (x4) - https://goo.gl/QJfHnv
五:手機 App 程式設計(總計:x100)
[iOS](小計:x4)
> Objective C Tutorial (x4) - https://goo.gl/GswBRH
[Android](小計:x96)
> How to Make Android Apps (x29) - https://goo.gl/DjgL2R
> Android Development for Beginners (x25) - https://goo.gl/LvVODL
> Android Development Tutorial (x42) - https://goo.gl/jm1U5J
六:嵌入式系統(總計:x4)
> 電子電路 Electronics Tutorial (x4) - https://goo.gl/u1Ue0Y
七:軟體工程(總計:x65)
> 物件導向設計 Object Oriented Design (x11) - https://goo.gl/XTcjaa
> UML 2.0 Tutorial (x9) - https://goo.gl/ZF4Coq
> Design Patterns Video Tutorial (x27) - https://goo.gl/znWcZ9
> 重構 Code Refactoring (x18) - https://goo.gl/rNNpUY
八:軟體開發工具(總計:x4)
> Git Video Tutorial (x4) - https://goo.gl/lwtZfF
如果您覺得這個頻道太讚了!或覺得我整理順序、縮網址、算影片數目,搞了三個多小時沒功勞也有苦勞,那就麻煩您按讚鼓勵一下小弟吧!當然更歡迎您轉發給 Facebook 上的朋友,讓他們也感受一下什麼是「倒抽一口涼氣」... XD。有任何不瞭解的,歡迎在下方留言一起討論喔!
assembly code教學 在 紀老師程式教學網- [免費影音教學] x86 組合語言簡介(Intro to ... 的推薦與評價
[免費影音教學] x86 組合語言簡介(Intro to x86 Assembly Language) YouTube 播放清單: https://goo.gl/qwA6ah... ... <看更多>
assembly code教學 在 紀老師程式教學網- [免費影音教學] x86 組合語言簡介(Intro to ... 的推薦與評價
[免費影音教學] x86 組合語言簡介(Intro to x86 Assembly Language) YouTube 播放清單: https://goo.gl/qwA6ah... ... <看更多>
assembly code教學 在 [心得] 個人的x86 組合語言觀念筆記- 看板ASM - 批踢踢實業坊 的推薦與評價
※ [本文轉錄自 C_and_CPP 看板 #1Cis6A7c ]
作者: purpose (purpose) 看板: C_and_CPP
標題: Re: [問題] C/C++ 中的 asm 該如何學起?
時間: Tue Oct 12 03:12:07 2010
發篇筆記
一、[簡介] 機器語言與80x86
二、[觀念] 組合語言—Intel Style 與 AT&T Style、MASM 與 NASM
三、[教學] 簡單連結範例—NASM 組合語言 與 C/C++ (Windows 平台)
-------------------------------------------------------------
一、[簡介] 機器語言與80x86
大家家裡用的計算機器叫做個人電腦 (PC)。
可以拿來安裝 Windows、Linux,甚至 Mac OS X...等作業系統。
個人電腦的 CPU 演變歷史,可以說就是 Intel 的歷史。從最早的16位元CPU:
「8088/8086 -> 80286」,再演化到32位元的 80386、80486...
後來因為商標不能用數字註冊,Intel 不使用 80586 命名,從586開始,
改名為歷史上的 Pentium CPU。
AMD 也差不多是在 Pentium 時代開始慢慢成為 Intel 在個人電腦處理器上的
競爭者。
可以想見 Intel 就是個人電腦處理器的「唯一制定者」,Intel自己做新的 CPU
也要向後相容以前的東西,就像 Windows 7 也得要能執行 Windows XP 的程式一般。
你在 286 寫的程式,拿去給 486 的 CPU 也要能跑。
所以「個人電腦 CPU = x86 家族」...好啦,可能有人不認同這句話。
你跟美國人講話就要講英文、跟法國人講法文;
跟 x86 家族的處理器講話,就要講「x86 機器語言」;
跟 Intel 8051 單晶片處理器溝通,就講「8051 機器語言」;
在算盤本裡面介紹的處理器是「MIPS」家族,就用「MIPS 機器語言」跟其溝通。
所有處理器裡面,x86家族功能當然是最強,每一代都有增加新功能,又要向後相容,
所以其實該語言最複雜、不規則、不好學。但只用些基本的功能的話,還是過得去的。
二、[觀念] 組合語言—Intel Style 與 AT&T Style、MASM 與 NASM
機器語言因為電路關係,原始形式就是 010101 這種二進位形式,但你喜歡也可以
轉成十六進位寫出來給別人看。
下面這是一個 x86 機器語言指令 (instruction):
05 0A 00 00 00 (十六進位表示)
用人類說法就是你告訴某顆 x86 家族的 CPU:
「把你的 eax 暫存器內容取出,將其跟10相加,再把結果寫回 eax 暫存器」
用C語言表示法就是:
「eax += 10;」
機器語言形式顯然太麻煩了。
於是發明了「助憶符號」,比如用 add 代表「相加這個運算動作」;
減法動作,用符號 sub 標記;
將資料從A處複製過去B處的動作,就用 mov 助憶符號標記。
add, sub, mov...等是運算子,而 eax 暫存器跟 10 是運算參與單元 (運算元)。
如果綜合以上講的運算子跟運算元,想要寫出完整指令時,還會有一個問題!
若有 eax, ecx 兩個運算元,想要把 eax 的值取出,複製到 ecx 去
到底該寫 mov eax, ecx 還是 mov ecx, eax ?
哪邊來源?哪邊目的?
AT&T、Intel 各自有一套語法慣例。
詳細資料參考這裡:
https://www.ibm.com/developerworks/library/l-gas-nasm.html
C語言 Intel AT&T
指派運算子的 靠最左邊的運算元 靠最右邊的運算元
左邊是目的地 是目的地。 是運算結果放置處。
int eax = 4; mov eax, 4 movl $4, %eax
(暫存器名稱前,需加 % 符號;
而且4這個立即數值前,需加 $ 符號;
且用 movl 表示 move long 這麼長)
西瓜靠大邊,跟大家一起用 Intel 慣例的寫法就好。
像上面 mov eax, 4 這樣子的指令形式,都叫「組合語言」,說穿了只是把當初
的「x86 機器語言」寫成比較容易看懂的形式而已。
既然這樣,那 x86 機器語言就一套,助憶符號跟暫存器也固定那幾個。
為什麼最後卻搞出 MASM、TASM、NASM、FASM...這麼多種組合語言呢?
MASM,軟體界霸主微軟推行的組合語言 (雖然微軟最近變心去搞 MSIL 的樣子?)
NASM,在台灣是僅次於微軟的選擇方案,而且跨多個作業系統平台。文件完整、
有中文書籍在講它,而且狀態穩定。
※ FASM,較新,類似於NASM,聽說比較快?
※ TASM,老牌子,現在很少人用了,但是有 Turbo Debugger 很強大,可以
對 16 位元執行檔做偵錯,偶爾也值得一用。
上面提到的組合語言都是 Intel Style,而 AT&T 會看到的地方,就是使用「gcc -S」
功能時會出現。但是可以用 objdump 去看 Intel Style 的組語。
如果是 gdb 偵錯則直接就有選項 disassembly-flavor intel 可以切換到
Intel 風格組語。
分別用 VC 跟 GCC ,一樣寫個 C 語言動態函式庫,把某個函數輸出,
我用 VC 時,可以在函數前面加上 __declspec(dllexport) 告知 VC 將該函數輸出。
也可以寫個「模組定義檔」(*.def) 去記載哪些函數要輸出。
但是這兩個方法都是 VC 特有,不是 C 語言規定的。
同樣狀況在組合語言亦同,MASM 也有一些組譯器指令是其專有,而 NASM 沒有。
甚至在語法上,兩者也有差異。
詳細資料:https://www.nasm.us/doc/nasmdoc2.html#section-2.2
NASM 對於「LABEL 符號」是有分大小寫的,MASM 沒有分。
(這不包含暫存器名稱,沒必要非寫 eax 而不寫 EAX,
也不包含 NASM 的假指令,比如 SECTION 大小寫都可以。)
而且 MASM 有些遭詬病的語法規範,NASM 有對其改進之。
對於 MASM
「某符號」要拿來「當成記憶體位址」用時,需加上 offset 修飾。
對於 NASM
覺得微軟這樣太麻煩,直接寫就一定是當位址用,不用加 offset。
MASM 的毛病是
如果寫組譯器指令如下,去定義兩個符號: (MASM、NASM 都支援這兩個假指令)
foo equ 1
bar dw 2
equ 指令很類似 #define pi 3.141421356 (...偷用別人的梗)
dw 這個假指令,是告訴組譯器把現在這個地方,所對應的記憶體位址,
取別名叫 bar,並且寫入 double-word (4位元組) 到此處,
存放值為 0x 00 00 00 02。
此時,如果 MASM 有兩個指令如下:
mov eax, foo ; foo 因為是 equ 設定出來的值,所以 eax 會得到 1
mov eax, bar ; bar 因為是「組合語言變數」,故 eax 得到值是 2
上面兩個指令,語法格式看起來完全一致,
但如果沒去觀看 foo 跟 eax 的假指令定義,就不能判定機器碼該翻成哪個。
如果是用 NASM,因為他強制規定只要是「間接取值」者,一律需加上中括號 []。
這個間接取值,意思是第一次取到的值,不是我要的,第二次取到的值才是我要的。
可以想成「間接取值 = 二次取值」。
換言之
mov eax, foo
mov eax, bar
對於 NASM 來說,不需要去看假指令定義,
因為 bar 跟 foo 都沒有用中括號,所以兩個都做一次取值就好。
亦即 mov eax, bar 會得到 bar 對應的記憶體位址,而不是 bar 的存放內容 2。
可是 MASM 很沒規律,因為 foo 是 equ 所定義,所以 eax 得到 1 (沒間接取值);
因為 bar 是一個 dw 定義的「組合語言變數」,所以將會做「間接取值」,先取得
bar 所對應的記憶體位址後,再到該位址再取一次值。
抓4位元組得到 2 來傳給 eax。
三、[教學] 簡單連結範例—NASM 組合語言 與 C/C++ (Windows 平台)
存成檔案 xx.c
---------------------------------------
#include <stdio.h>
int plusTen(int val);
int plusEleven(int x) {
return x+11;
}
int main() {
printf("return = %d\n", plusEleven(1));
printf("return = %d\n", plusTen(0x00123456));
return 0;
}
---------------------------------------
用 VC 編譯器的話,執行指令 cl /c xx.c 可以獲得對應的目的檔「xx.obj」
從 Visual Studio 200X 命令提示字元,去下這個 cl 指令,以省略環境變數的設定
存成檔案 fun.asm
---------------------------------------
section .text
global _plusTen
_plusTen:
push ebp ;函數初始化工作
mov ebp, esp ;函數初始化工作
mov eax, 0
mov ax, word [ebp+8]
add eax, 10
pop ebp ;函數結尾工作
ret ;返回呼叫函數 (caller),eax 是存放返回值用
---------------------------------------
去下載 NASM
https://www.nasm.us/pub/nasm/releasebuilds/2.09.02/win32/nasm-2.09.02-win32.zip
解壓縮後,執行指令 nasm -f win32 D:\Desktop\fun.asm
就能獲得一樣是 COFF 格式的目的檔 fun.obj。
簡單來說就是這個目的檔跟 cl /c 得到的目的檔有一樣格式。
最後再去「Visual Studio 200X 命令提示字元」下指令 link xx.obj fun.obj
就能得到 xx.exe 完成 C/C++ 跟 NASM 函數的連結了。
※對原理有興趣可以參考《程式設計師的自我修養》一書。
section .text
是 NASM 假指令,表示從這行指令以下的內容,翻譯成機器碼後
都要放到 .text 區去。每個 *.obj、*.exe 內部都有 .text 區段。
global
是 NASM 假指令,在這裡表示要把 _function 標籤包括的機器碼
視為全域函數。
在 C/C++ 你預設寫個函數,像上面的 plusEleven() 就自然會是
這裡說的這種「全域函數」。
使用 dumpbinGUI 工具,跳去 xx.obj、fun.obj 查看符號表,就可
看到 external 字眼。表示 xx.obj 可以調用 fun.obj 裡面寫
external 的符號,反之則反。
mov ax, word [ebp+8]
這個 ebp+8 是代表 plusTen() 函數的「參數1」
查一下 stack frame 的觀念,再用偵錯軟體觀察「函數呼叫」
進入前後的堆疊、暫存器變化,應該就能理解。
要說明的是,[ebp+8] 是「二次取值」,當第一次取值得到
ebp+8 位址假設是 0x0012FF74,接著要在這個地方做二次取值,在
C/C++ 要取幾個位元組的值是看該指標的資料型態。
如果是在 MASM,則是在中括號前寫 word ptr [ebp+8] 代表 2位元組;
若寫 byte ptr [ebp+8] 則代表 1位元組。
而 NASM 跟 MASM 差不多,但必須拿掉 ptr 字眼,否則會組譯錯誤。
因C語言還沒公開前,就已經留下一堆目的檔、一堆函數,他們都用正常的命名,
一些好記的名字都被用過了,所以 C 語言在使用函數時,其實一律自動加 _ 來命名。
因此原本的 C 函數呼叫雖然是 plusTen,但在 fun.asm 裡輸出的全域函數要寫成
_plusTen 才能讓 xx.c 可以連結到。
------------------------------------
關於「目的檔」,參考這篇:
https://en.wikipedia.org/wiki/Object_file
都只講微軟平台
在 DOS 時代目的檔名稱也都是 *.obj;執行檔名稱也都是 *.exe,
但這裡的 *.obj 其實是 OMF 格式 (Relocatable Object Module Format)。
跟你在 Windows 用 VC 編譯出來的目的檔 *.obj 不同。
用 nasm -f obj fun.asm 應該就是產生 OMF 格式的目的檔?
用 nasm -f win32 fun.asm 則是產生 COFF 格式的目的檔。
更精確來說這個 COFF 格式是微軟修改過的變種 COFF 格式。
你也可以叫它 PE/COFF 格式,甚至叫他 PE 格式也行,要解讀 PE 格式,其
第一選擇當然也是微軟提供 dumpbin,而軟體 dumpbinGUI 是圖形介面的前端。
真要說的話 dumpbin 也是一個前端,其實是呼叫 VC 的 link.exe,給隱藏選項
link /DUMP /ALL xx.obj。
https://en.wikipedia.org/wiki/Portable_Executable
PE 格式不一定是 *.exe 執行檔,也可以是 *.dll 也可以是 *.obj...等。
因為 VC 編譯出的目的檔都是 PE 格式,而 VC 的 link.exe 不能處理古早的
目的檔 OMF 格式,所以上面需要叫 nasm 用 -f win32 選項去產生 PE 目的檔。
也許會有某個很強的連結器,可以把 OMF 跟 PE 目的檔連結成 PE 執行檔吧?
甚至把 gcc 編出來的目的檔跟 PE 目的檔 link 成 PE 執行檔?
看有沒有人知道囉
------------------------------------
MASM 組譯器指令清單 https://msdn.microsoft.com/en-us/library/8t163bt0.aspx
剛好看到,補充上來。
------------------------------------
(怕以後忘記,再寫篇記錄,上文不變動,新增內容於下)
16位元記憶體模型—Segment:Offset (分段記憶體模型)
32位元記憶體模型—Flat Memory Model 加 Paging
( https://en.wikipedia.org/wiki/X86_architecture )
個人電腦 x86 家族的 CPU,在 16 位元時代是 8088/8086/80286 這三位;
而 x86 家族第一個 32 位元始祖是劃時代的 80386。
8088跟8086的位址匯流排都有20條線,每條線都是一端連接記憶體,一端連接
處理器,在高低電位變化下 (0、1),總共可有 2^20 種控制變化。
換言之,依照每個記憶體位址對應一個位元組的慣例,可以定位 2^20 大小的
記憶體位址;
80286 則進化到 24 條位址線,定址能力達 2^24,即 16M 記憶體。
省麻煩,把它當成跟 8088/8086 一樣,只能定址到 1M 記憶體就好。
※ 在 x86 的術語中,記憶體位址可以分成三種:
邏輯位址、線性位址、真實位址(物理位址)
必須先「邏輯位址→線性位址」,然後接著才是「線性位址→真實位址」。
自從32位元 CPU 出現 (自 80386 後),記憶體 Model 變成 Flat Memory,
邏輯位址就已經等於線性位址了。
然後是因為有「分頁機制」武力介入,所以需要先透過分頁機制轉換,線性位址
才會變成真正的物理位址。
而分頁機制是從 80386 開始使用 (保護模式的完整版也是從 80386 開始)。
那為什麼 16 位元處理器,不使用 Flat Memory Model?為什麼當初的邏輯位址
要先經過轉換才會變成線性位址?
因為16位元CPU內部,參與運算的暫存器當時都還停留在16位元
(如:ax, bx, cx, dx),甚至最重要的指令暫存器 (IP) 也是 16 位元,
故只有定位到 0~65535 也就是 64K 記憶體的能力。
Intel 用額外提供的四個「分段暫存器」(CS、DS、ES、SS),搭配其他暫存器後
,使得每次記憶體定址方式其實是 Segment:Offset,此時這種位址表達法叫
邏輯位址。
CS 是 Code Segment、DS 是 Data Segment、SS 是 Stack Segment ...
邏輯位址→線性位址,公式是:「Segment Register * 0x10 + Offset」
假設我們有個程式,裡面的「全域變數」(不是放在堆疊的那種區域變數),
有個很大的整數陣列,總共有 128K。當程式執行時,這些資料區段假設放在
「線性位址=物理位址」的 0x0 ~ 0x1FFFF 這段連續的記憶體空間裡。
在邏輯位址(以寫程式的角度去觀看的位址),這 128K 資料會被分成兩段,
第一段是 DS=0 且 offset = 0x0000~0xFFFF,第二段是 DS=1 且 offset
= 0x0000~0xFFFF。
換言之,如果我要把某陣列元素移到 ax 暫存器,指令可能長這樣
mov ax, word ptr[0x1234]
如果執行這行時 DS=0,則會取到物理記憶體位址 0x01234 處 (第一段);
如果執行這行時 DS=1,則會取道物理記憶體位址 1*0x10 + 0x1234 = 0x11234。
若要讀取最後一個位元組到ax,只要執行以下指令即可:
mov ds, 1
mov al, byte ptr[0xFFFF]
因為不知道當時的 DS 值為多少,所以保險點,先設定 DS,然後因為 ax 是
16 位元,不必用到這麼大。所以用 al 存放即可。 al 就是 ax 暫存器
低 8 位元別名。
(ax 在 32 位元以上的 CPU 時,其實也是 eax 暫存器的低16位元處別名)
現今的執行檔,比如 PE 執行檔,往往內部都有分 .text (.code)、.data,
可能就是承襲當初的記憶體分段機制?
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 124.8.140.240
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 124.8.128.171
... <看更多>