一個有趣的過程movq%rcx,%gs:0x80000000不能通過編譯

stormbm發表於2018-10-18

movq %rcx, %gs:0x80000000不能通過編譯

今天有同事提問, 為什麼

movq %rcx, %gs:0x7fffffff //可以通過編譯
movq %rcx, %gs:0x80000000 //不能通過編譯

其實就是一個立即數的差別, 應該是無差的, 好吧, 讓我們來研究一下

第一步

先看一下movq %rcx, %gs:0x7fffffff的二進位制指令, 因為movq %rcx, %gs:0x80000000不能通過編譯, 就不是合法指令, 自然也看不了二進位制內容了

  1c:   65 48 89 0c 25 ff ff    mov    %rcx,%gs:0x7fffffff
  23:   ff 7f

第二步

既然65 48 89 0c 25 ff ff ff 7f是mov %rcx,%gs:0x7fffffff, 很容易得到前面65 48 89 0c 25是mov %rcx,%gs, 後面是一個立即數, 那麼我們就看看65 48 89 0c 25 80 00 00 00是什麼東西

第三步

怎麼知道65 48 89 0c 25 80 00 00 00是什麼東西呢?
開啟o檔案, 找到指令的編碼位置, 把7f ff ff ff 改成 80 00 00 00, 然後再objdump, 得到

  1c:   65 48 89 0c 25 00 00    mov    %rcx,%gs:0xffffffff80000000
  23:   00 80

由此我們可以得到, 65 48 89 0c 25後面的4位元組會被解釋成signed int, 再來驗證一下, 編譯一下mov %rcx,%gs:0xffffffff80000000可以得到65 48 89 0c 25 00 00 00 80

可以得到結論, movq %rcx, %gs:0x80000000被gcc理解成了movq %rcx, %gs:0x0000000080000000

第四步

下面的問題就是為什麼movq %rcx, %gs:0x0000000080000000是非法指令
其中一個很容器想到的答案就是, 原來指令65 48 89 0c 25後面是跟4位元組的, 現在變成8位元組了, 如果還是這種模式的話, 變成了13位元組, 應該是太長了, 所以65 48 89 0c 25要變成更短的東西, opcode和register的選擇也沒這麼隨意了, 下面的事情就是去查手冊了, 還有一種偷懶的方式就是把rcx換成其他暫存器, 把所有的暫存器都試一遍, 看看行不行

結論

movq %r?x, %gs:0x0000000080000000只能使用rax

movq %rcx, %gs:0x7fffffff, movq %rcx, %gs:0x80000000是被編碼成64位基地址和32位整數或者64位整數相加,

先討論位編碼成32位整數的情況,

如果我是設計師的話, 也傾向於把後面的這個32位設計成signed, 這樣定址過程中, 又能向前, 又能向後

所以movq %rcx, %gs:0x7fffffff是合法的

movq %rcx, %gs:0x80000000 gcc報告錯誤是因為這個等價於movq %rcx, %gs:0x0000000080000000, 0x0000000080000000已經超出32位signed的範圍了, 不能編碼成64位基地址和32位整數相加, 只能編碼成64位基地址和64位整數相加

這樣後面的這個64位整數就要被編碼進指令裡面去, 比之前的指令多了4個位元組, 所以前面的暫存器, opcode的編碼就少了, 不能隨意的選擇暫存器, 只能預設rax來操作, 也就是mov變成了movabs

所以movq %rax, %gs:0x80000000是合法的


相關文章