My code works, I don’t know why.

國王的耳朵是驢耳朵

Using as 手冊筆記

| Comments

先承認我自己很不滿意這篇,太亂了。只能當工具查keyword用。不過as 手冊的確就是指令和語法。原本是以英文字母順序說明,我只是把這些用自認的方式重新分類。很多地方也真的只有句意翻譯。就把他當作看手冊的導讀,有找的需要的再進去看手冊吧。

本篇只討論ELF部份,其他binary format跳過。

目錄

as參數

只提幾個我有興趣的部份

  • -Z:硬上,就算有錯誤照樣組譯沒有錯的部份。
  • --gstabs+:好東西,可以幫你加入debug資訊,然後直接用gdb除錯。
  • 如果檔案副檔名為.s,就是普通的組合語言原始檔。
  • 如果檔案副檔名為.S,就可以使用cpp(還記得c preporcessor吧?)來處理前置處理。

名詞解釋

  • symbol:由字母、數字、和_.$組成的字串。不得以數字開頭。
    • labelsymbol後面加:
  • .開頭的symbol是gas 的directive
  • expression:運算式,結果代表不是位址就是單純的數字
  • 原始碼不是以上的情況,由英文字母開頭組成的字串就是instruction
  • 原始碼最後一行一定要是\n。目前網友Carl有提供為什麼這樣規定的link

常數

  • 字元常數:
    • '字元
    • 顯示\: '\\
  • 字串:"字串"

Section

一個連續的記憶體空間。這段連續空間都是為了處理某些單一特定的任務如執行程式碼、存放global變數等。

題外話,.bss存在的目的是節省儲存空間,沒有初始的全域變數當然不需要在檔案中保留儲存空間。

undefined section

在組譯的時段只要位址無法決定的symbol,一律放到undefined section。然後祈禱linker幫你搞定。

relocation

前面的文有提到,linker功能之一就是把不同的object檔案成一個執行檔。要怎麼呢?

每個object 檔案的起始點都是address 0。由linker計算並設定每個object檔案最後在執行檔放置的address,避免這些object的內容互相覆蓋。

而linker要怎麼搬移和設定最後的位址呢?這是因為object檔案內已經有規範好的不同名稱的好幾個連續空間,也就是section。所以linker把這些object檔案中相同section名稱的連續空間搬到執行檔內相同名稱的空間,並且保證執行檔內這些section的空間也是連續的。而搬移的動作並設定section的runtime address就稱為relocation

Linker在relocation時需要考慮的問題,as也幫他處理了,這些問題是

  • 目前這個位址要對應到object檔案的哪個地方?
  • 這個位址會需要佔用多少byte的空間?不懂?int和char吃的空間總會不一樣吧。
  • 目前位址對應到的是哪個section? 這個位址和對應section的offset為何?
  • 目前的位址是絕對位址還是和program counter相對的位址?

另外要注意的是,大部分的位址可以表示成

1
 (section) + (offset into section)

Expression

expression的結果代表不是位址就是單純的數字。這些數字要嘛是絕對位址、要嘛就是某個section的offset。而expression之間可以有空白。

Empty expression

空白字元或是null,其值會被設為0

Integer expression

由一個以上的argument和operator組成的expression

Arguments

包含 symbols, numbers 或subexpressions,分別討論

  • symbol:結果將會是 {section setction的offset數值},數值會是32位元的二的補數(就是有正負值啦)
  • numbers:一般來說,是正整數。如果你要處理浮點數或是大數(超過32位元的數字)as會噴警告。你需要自己處理這種情況。
  • subexpressions:指的是
    • (expression)
    • prefix operator 伴隨一個 argument

Operators

用來協助運算section中的offset位址。

  • Infix Operators
    • 就一般的binary operator如+, -
  • Prefix Operators
    • -:負號
    • ~:補數,就是將argument的每個位元inverse

Infix也和C語言一樣,有優先順序、符號定義也大致相同,列出如下

  • 最優先
    • *, /, >>, <<
  • 第二順位
    • |, &, ^, !
  • 第三順位
    • +, -, ==, <>, !=, >, <, >=, <=
      • <>就是!=
  • 最低順位
    • &&, ||

directives

重頭戲。directive又稱pseudo-ops,一律以.開頭。照字面理解,這東西是用來協助使用開發,而不是真正的CPU instruction。這邊我只列出看得懂我感興趣的部份。有興趣請參考出處。另外和硬體相依的directive請參考這邊

變數相關

  • .ascii "字串":可以用多個字串,中間以,隔開。這些字串最終會被一起放在連續的記憶體中。
  • .asciz "字串":和樓上的差別是字串後面會自動填\0,和C語言的字串表示方式相同。
  • .balign[wl] abs-expr, abs-expr, abs-expr:和.align差別在b是byte,w是2-byte,l是4-byte。這代表什麼呢?代表要pad的數字(如果有指定的話)要注意fill byte數量。如.balignw 8, 0xbeef
  • .byte expressionsexpression數量可以從0個到多個,中間以,隔開。這些expression會依照順序排列。那麼要幹什麼用呢?你可以這樣玩。
1
.byte 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00 /* "Hello" */

還有其他玩法,請參考這邊這邊

  • .int expressions
  • .long expressions
    • 上面兩個有同樣效果,expression為16-bit寬度。可以用,隔開。和.byte用法類似。長度以及order會和CPU架構相關。
  • .hword expressions
  • .short expressions
    • 上面兩個有同樣效果,expression為16-bit寬度。可以用,隔開。和.byte用法類似。
  • .double flonums:就浮點數,可以用,隔開。和.byte用法類似。表示方式要看target CPU架構。
  • .float flonums:就浮點數,可以用,隔開。和.byte用法類似。表示方式要看target CPU架構。
  • .lcomm symbol, length:為symbol保留length的空間,該symbol型態不會是global,並且會被放在.bss section。
  • .octa 大數字:為16-byte寬度。可以用,隔開。和.byte用法類似。
  • .quad 大數字:為8-byte寬度。可以用,隔開。和.byte用法類似。
  • .string "字串":將字串放到object file中,看不出來和.ascii差在那。
  • .string16 "字串":將字串放到object file中,字串中的單個字元將會展開成2個bytes。看不出來和.ascii差在那。
  • .string32 "字串":將字串放到object file中,字串中的單個字元將會展開成4個bytes。看不出來和.ascii差在那。
  • .string64 "字串":將字串放到object file中,字串中的單個字元將會展開成8個bytes。看不出來和.ascii差在那。
  • .set symbol, expression:將symbol的值設成expression的值。
  • .size symbol, expression:設定symbol空間為expression的值。

Symbol的描述

visibility:local, global or weak

  • .extern:單純是相容性使用,特地列出來只是因為手冊說as將所有undefined symbols視為extern
  • .global symbol
  • .globl symbol
    • 以上兩個同樣效果,就是讓linker看得到這個symbol,也就是說透過nm觀察binary也可以看得到這些symbol
  • .local symbol:讓linker到這個symbol。手冊上另外有提到.local不支援alignment的問題和解法我看不懂,有興趣自行去連結參考。
  • .weak symbol:組譯器找不到symbol會產生一個。

Symbol type

  • .type symbol, type:type 描述方式有五種。我只用我看順眼的那種說明。
    • "function":這個symbol是個function
    • "object":這個symbol用來存放資料
    • "tls_object":這個symbol用來存放thread local資料
    • "notype":沒有指定
    • "gnu_unique_object":保證該symbol是唯一的symbol
    • "gnu_indirect_function":看不懂

其他Symbol 相關

  • .desc symbol, abs-expression:提供描述symbol的特性,細節請參考前面的說明。
  • .equ symbol, expression:將symbol設成expression的值
  • .equiv symbol, expression:和上面類似,但是如果該symbol之前已經定義過,就會噴錯誤。

Section

  • .data:不解釋
  • .test:不解釋
  • .section name:讓as把以下的東西組成name的section。名字雖然可以亂取,但是也要看binary format有沒有支援。如a.out就沒有這東西。

ELF 下的Section directive

ELF的話,這個directive有加料。說明如下: []表示optional

  • .section name [, "flags"[, @type[,flag_specific_arguments]]]
    • flags:可由下面的flag合體組成
      • a:allocatable,就是要在記憶體內吃空間,但是loader不一定會載入東西到該section
      • e:非executable或是shared library的section
      • w:可寫入
      • x:可執行
      • M:可被merge
      • S:該section有 zero terminated 字串
      • G:屬於某個section group
      • T:給thread local存放東西用 (存放三小?)
      • ?:看不懂,跳過
    • type
      • @progbits:section有資料 (怎麼有種廢話的感覺?)
      • @nobits:沒有資料,如.bss這樣的section
      • @note:不是給程式執行的時候使用的section
      • @init_array:該section 有個pointer arrary 指到init 函數(補充說明1 補充說明2)
      • @fini_array:該section 有個pointer arrary 指到fini 函數
      • @preinit_array:該section 有個pointer arrary 指到pre-init 函數

由於@在某些平台如ARM上是註解的符號,這種情況需要用%替代。

GM有特別規範,必須隔離在雙引號外面。而同時要用這兩個flag要以MG順序擺放,範例如下:

  • .section name , "flags"MG,...

Section group目前先假裝沒看到,有機會又看到再回來討論。

條件以及控制相關

  • if 部份有點雜亂,懶得想範例測試,想像成C語言的#ifdef。剩下自己看手冊
  • .irp symbol,values...:和巨集概念很類似,把.irp ....endr之間的instruction用到symbol的部份全部換成value。範例如下。

.irp item, 2, 3, 4 mov %r\item, $\item .endr

會展開成

1
2
3
mov %r2, $2
mov %r3, $3
mov %r4, $4
  • .irpc symbol,values...:手冊上面的說明幾乎和irp相同,悲劇的是範例和.irp完全一致。唯一差別是.iprc中有提到character,只能猜測c是character。
  • .offset loc:將locale counter設定成loc。
  • .org new-lc, fill:同樣是更動locale counter,但是只能在同一個section中移動。另外一個要注意的是這個指令只能增加locale counter,硬要減少是不可能的。當locale couter移動後,中間的空白會填入fill的值。不加上, fill as會填0。
  • .rept 次數:重複.rept.endr指定的次數。
  • .skip size, fill:產生size長度,fill值的資料。
  • .fill repeat, size, value:產生value,佔用空間為size。是否要產生多個,否的話repeat0,是的話repeat填要產生的個數。sizevalue為optional,size預設為1value預設為0

    • .fill 2,,
    • .fill 2,,10
    • .fill 2,4,
  • .warning "string":印出警告訊息。

  • .err:噴錯誤,除非as有-Z指令,不然別想產生obj檔。
  • .error "錯誤訊息":印出錯誤訊息然後GG。不帶錯誤訊息as會印出檔案名稱和用了.error那行。

  • .fail expressionexpression值大於五百噴警告,小於五百噴錯誤。用在複雜的巢狀巨集或是條件式組合語言中。

  • .print "字串":組譯的時候stdout會印出字串。
  • .end:表示組合語言程式結束

巨集

跳過,自行看手冊

ELF相關

  • .symver symbol, symbol2@nodename:指定symbol的版本號碼,一般用在shared library中。詳細說明懶得看,那天GG再回來看。

ELF section stack

  • .subsection name:把目前的section push到section stack中,並且把目前的subsection置換成name
  • .popsection:從section stack中pop最上面的section去覆蓋目前的section
  • .pushsection name [, subsection] [, "flags"[, @type[,arguments]]]:把目前的section push到section stack中,並且把目前的section置換成name以及subsectiontypeargument.section的參數相同。

ELF visibility

  • .protected symbol:不但外部看不到該symbol,連內部要使用讀取該symbol的另外一個symbol也要在內部定義。直接舉個虛擬C語言。
ex.c
1
2
3
4
5
static int whatever = 1;
void func(void)
{
    int local = whatever;
}
  • .hidden symbol:想像C語言在function前面加上static,觀念類似,讓該symbol無法被其他component看見。手冊這樣的symbol通常被視為.protect symbol,目前懶得寫程式測試。單純猜測這兩個有不同,不然幹嘛要分成兩個指令。
  • .internal symbol:手冊上提到除了和.hidden有同樣效果外,不同的CPU會針對這個symbol做特別處理,到底是哪些特別處理,手冊沒說。

除錯相關

大部分跳過,太多背景需要補完。

  • .def
  • .endef
  • .dim:給compiler產生除錯用。
  • .file 檔案行號 檔名DWARF2用的除錯,除錯時對應的原始碼行號。
  • .func name[,label]:只有開啟除錯有效,必須在結尾加入.endfunclabel就是組合語言內的label,也就是該function的進入點。不填的話,就在name加上prefix 字元當作進入點,通常prefix字元為_

  • .loc fileno lineno [column] [options]DWARF2用的除錯。整理如下

    • 手冊假設我們很瞭debug內部資訊,但是我不會。看下來他們有提到
      • .debug_line 狀態機
      • .debug_line line number matrix
      • 不明暫存器:is_stmt register,isa register等
    • 資訊放在binary 的.debug_line section。
    • 在debuger(?)載入.debug_line資訊時,讀到該行,會把參數fileno, lineno,等參數一併載入。
    • options:
      • basic_block:設定 .debug_line狀態為basic_block
      • prologue_end:設定 .debug_line狀態為prologue_end
      • epilogue_begin:設定 .debug_line狀態為epilogue_begin
      • is_stmt value:設定 is_stmt register 在.debug_line狀態為value,合法數值只有01
      • isa value:設定 isa register 在.debug_line狀態為value,合法數值只有01
      • discriminator value:設定 discriminator register 在.debug_line狀態為value,合法數值只有01
  • .loc_mark_labels enable:是否enble,basic_block register細節完全看不懂。只知道和debug line number entry有關。
  • .stabs symbol, type, other, desc, value:用來提供資訊給symbolic debuger。詳細資訊請看手冊
  • .tag structname:compiler產生的輔助directive。用來從symbol table中找出structname的instance。
  • .val addr:看不懂。自己看手冊。看起來是紀錄addr的值,但是怎麼會和symbol table扯上關係??

未分類

  • .include "file":從目前的位置,把file全部原封不動地放到之後的位置。
  • .align abs-expr1, abs-expr2, abs-expr3local counter (請參考前面linker script文)結束要對齊的位址倍數。
    • abs-expr1:必填。要對齊的數字。根據CPU,數字代表可能是byte,有些代表的是bit。所以要對齊8有的CPU要填8,有些CPU要填3。心理的OS,欠揍。
    • abs-expr2:optional。如果需要填空,可以指定填入的數值。不填就使用預設值,0。
    • abs-expr3:optional。指定跳過數字最多可以幾個,超過就直接不對齊了。(手冊用skip而不用pad讓我在想這到底差別在那?)

如果後面兩個都不想填,可以直接下.align abs-expr1,,收工。

  • .comm symbol, length:我是這樣理解啦,就是很多C語言檔案要用同一個全域變數。先摸到的先贏。可以看我以前整理的說明。另外有兩點要注意

    • 如果有同樣的symbol,在不同檔案中,設定的長度又不同,gas會選最大的。
    • ELF有隱藏的第3參數,用來指定alignment。
  • .gnu_attribute tag, value:GNU屬性自己查

  • .ident 字串:不同binary format有不同處理,在ELF中會把字串放到.comment section中。要注意,file包括command line參數中-I指定的路徑。
  • .incbin "file"[,skip[,count]]:從目前的位置,把file原封不動地放到之後的位置。你可以透過skip指定從檔案起始地幾個byte後跳過。另外你也可以透過count指定檔案最多include幾個bytes。

另外一點要注意,file包括command line參數中-I指定的路徑。

  • .version "string":產生.note section並且將字串放入該section。

參考資料

Comments