# 針對組譯器一次分析
https://github.com/programmermagazine/201306/blob/master/source/article5.md
作者是陳鐘誠老師,剛好也挖到了些源碼 來小分析一下
程式好像也沒漏掉什麼,編譯環境還是 DEVC++
剛好也跑得起來想說直接來針對這來分析一下,vscode 掛 GDB 好有點麻煩還有要配置檔以後有空弄
比較難得的是它裡面有寫註解,看起來是個好上手的東西
# 什麼是組譯器編譯器
什麼是組譯器應該是在 把 中間碼轉為 組合語言轉成 目的碼這塊 是交由 我們的 組譯器來跑
然後編譯器是把 高階語言 C /C ++ 翻成中間碼 在 翻成 機械碼
![](https://i.imgur.com/WROwvOQ.png)
所以類似這個概念
前端 中間碼 後端
那這個下面這個程式就是負責翻譯我們的組合語言 成 目的碼
# 先翻 Makefile
可以看到我們的 assmebler 在 as0 那一行
```makefile
CC = gcc.exe -D__DEBUG__
OBJ = Parser.o Tree.o Lib.o Scanner.o Array.o Compiler.o HashTable.o Generator.o Assembler.o Cpu0.o OpTable.o
LINKOBJ = $(OBJ)
LIBS =
INCS =
BIN = test.exe c0c.exe as0.exe vm0.exe
CFLAGS = $(INCS) -g3
RM = rm -f
.PHONY: all clean
all: $(OBJ) test c0c as0 vm0
test: $(OBJ)
$(CC) main.c $(LINKOBJ) -DTARGET=TEST -o test $(LIBS)
c0c: $(OBJ)
$(CC) main.c $(LINKOBJ) -DTARGET=C0C -o c0c $(LIBS)
as0: $(OBJ)
$(CC) main.c $(LINKOBJ) -DTARGET=AS0 -o as0 $(LIBS)
vm0: $(OBJ)
$(CC) main.c $(LINKOBJ) -DTARGET=VM0 -o vm0 $(LIBS)
clean:
${RM} $(OBJ) $(BIN)
Parser.o: Parser.c
$(CC) -c Parser.c -o Parser.o $(CFLAGS)
Tree.o: Tree.c
$(CC) -c Tree.c -o Tree.o $(CFLAGS)
Lib.o: Lib.c
$(CC) -c Lib.c -o Lib.o $(CFLAGS)
Scanner.o: Scanner.c
$(CC) -c Scanner.c -o Scanner.o $(CFLAGS)
Array.o: Array.c
$(CC) -c Array.c -o Array.o $(CFLAGS)
Compiler.o: Compiler.c
$(CC) -c Compiler.c -o Compiler.o $(CFLAGS)
HashTable.o: HashTable.c
$(CC) -c HashTable.c -o HashTable.o $(CFLAGS)
Generator.o: Generator.c
$(CC) -c Generator.c -o Generator.o $(CFLAGS)
Assembler.o: Assembler.c
$(CC) -c Assembler.c -o Assembler.o $(CFLAGS)
Cpu0.o: Cpu0.c
$(CC) -c Cpu0.c -o Cpu0.o $(CFLAGS)
OpTable.o: OpTable.c
$(CC) -c OpTable.c -o OpTable.o $(CFLAGS)
```
# 我們再來看 main.c
發現他是根據 makefile 裡面的 tag去產生 各個執行檔 我們看到了我們的 AS0這一塊
他接受兩個參數 這邊我有先大致看過了,大概是 輸入ASM 產生 OBJ
```c
#include "Assembler.h" // 引用組譯器檔頭
#include "Compiler.h" // 引用編譯器檔頭
#define TEST 1 // 編譯目標 1: test
#define C0C 2 // 編譯目標 2: c0c
#define AS0 3 // 編譯目標 3: as0
#define VM0 4 // 編譯目標 4: vm0
void argError(char *msg) { // 處理參數錯誤的情況
printf("%s\n", msg);
exit(1);
}
int main(int argc, char *argv[]) { // 主程式開始
char cFile0[]="test.c0", *cFile=cFile0; // 預設程式檔為 test.c0
char asmFile0[]="test.asm0", *asmFile=asmFile0; // 預設組合語言為test.asm0
char objFile0[]="test.obj0", *objFile=objFile0; // 預設目的檔為 test.obj0
#if TARGET==TEST // 如果編譯目標為 TEST
ArrayTest(); // 測試陣列物件
HashTableTest(); // 測試雜湊表物件
OpTableTest(); // 測試指令表物件
compile(cFile, asmFile); // 測試編譯器
assemble(asmFile, objFile); // 測試組譯器
runObjFile(objFile); // 測試虛擬機器
checkMemory(); // 檢查記憶體使用狀況
#elif TARGET==C0C // 如果編譯目標為 C0C
if (argc == 3) { // 如果有 3 個參數
cFile=argv[1]; asmFile=argv[2]; // 設定參數
} else // 否則
argError("c0c <c0File> <asmFile>"); // 提示程式執行方法
compile(cFile, asmFile); // 開始編譯
#elif TARGET==AS0 // 如果編譯目標為 AS0
if (argc == 3) { // 如果有 3 個參數
asmFile=argv[1]; objFile=argv[2]; // 設定參數
} else // 否則
argError("as0 <asmFile> <objFile>"); // 提示程式執行方法
assemble(asmFile, objFile); // 開始組譯
#elif TARGET==VM0 // 如果編譯目標為 VM0
if (argc == 2) // 如果有 2 個參數
objFile=argv[1]; // 設定參數
else // 否則
argError("vm0 <objFile>"); // 提示程式執行方法
runObjFile(objFile); // 開始執行 (虛擬機)
#endif
system("pause"); // 暫停 (給 Dev C++ 使用的)
return 0;
}
```
# input
```asm
LD R1, B
ST R1, A
JMP B
RET
A: RESW 1
B: WORD 29
```
# Assemble.c
那我們可以直接看到 我們的 主要進入店 他產生一組
**Assembler *a = AsmNew();
然後開檔讀入 text char
通過
** AsmPass1(a, text);**
**HashTableEach(a->symTable, (FuncPtr1) AsmCodePrintln);**
**AsmPass2(a); **
**AsmSaveObjFile(a, objFile);**
其中我們要注意的是我們的 Pass1 和 pass2 分別是對我們的
pass1轉成絕對定址, 和 對我們的 text 做初步 的分析
pass2轉成 相對定址 ,最後由 Pass2 產生 目的碼 HashTableEach 則是把我們的最終 asm 檔的 特殊符號 比如說變數的宣告等等 都丟到我們的 hash 表去做 符號表的 name 與 address。
```C
#include "Assembler.h"
void assemble(char *asmFile, char *objFile) { // 組譯器的主要函數
printf("Assembler:asmFile=%s objFile=%s\n", asmFile,objFile); // 輸入組合語言、輸出目的檔
printf("===============Assemble=============\n");
char *text = newFileStr(asmFile); // 讀取檔案到 text 字串中
Assembler *a = AsmNew();
AsmPass1(a, text); // 第一階段:計算位址
printf("===============SYMBOL TABLE=========\n");
HashTableEach(a->symTable, (FuncPtr1) AsmCodePrintln); // 印出符號表
AsmPass2(a); // 第二階段:建構目的碼
AsmSaveObjFile(a, objFile);
AsmFree(a); // 輸出目的檔
freeMemory(text); // 釋放記憶體
}
```
這邊我們看到 AsmNew 建構元做了什麼事
codes 是指令集 list
symTable 這邊是塞符號表
opTable 是塞 opcode table
```c
Assembler* AsmNew() {
Assembler *a=ObjNew(Assembler, 1);
a->codes = ArrayNew(1);
a->symTable = HashTableNew(127);
a->opTable = OpTableNew();
return a;
}
```
# lib.h
可以看到 ObjNew 這邊 我們追到了 lib.h
```c
#define ObjNew(type, count)newMemory(count*sizeof(type))
```
# lib.c
```c
// 記憶體配置函數
int newMemoryCount = 0;
void* newMemory(int size) {
void *ptr=malloc(size);
assert(ptr != NULL);
memset(ptr, 0, size);
// printf("memGet:%p\n", ptr);
newMemoryCount++;
return ptr;
}
```
所以應該是申請一個空間,來制定我們的Assembler a大小?
# ArrayNew
這邊又知道我們的 codes 是用來儲存我們拆成指令 陣列
![](https://i.imgur.com/bM7gLVo.png)
```c
a->codes = ArrayNew(1);
```
# Array.c
```c
void ArrayAdd(Array *array, void *item) {
ASSERT(array->count <= array->size);
if (array->count == array->size) {
int newSize = array->size*2;
void **newItems = ObjNew(void*, newSize);
memcpy(newItems, array->item, array->size*sizeof(void*));
printf("array grow from %d to %d\n", array->count, newSize);
ObjFree(array->item);
array->item = newItems;
array->size = newSize;
}
array->item[array->count++] = item;
printf("add item = %s\n", item);
}
```
# symTable
這邊是塞符號表
也就是在 ASM 裡面的 :a :b這些 符號
它們會根據我們一開始制定的規則 已經查詢過的運算碼 新增到 hash表 變為唯一值
![](https://i.imgur.com/qg4YtGJ.png)
# opTable
CPU0 的指令分為三種類型,L 型通常為載入儲存指令、A 型以算術指令為主、J 型則通常為跳躍指令,下圖顯示了這三種類型指令的編碼格式。
![](https://i.imgur.com/c4O8xC5.png)
下面是 cpu0 指令表
![](https://i.imgur.com/rfIVFlH.png)
# AsmPass1
```c
void AsmPass1(Assembler *a, char *text) { // 第一階段的組譯
int i, address = 0, number;
Array* lines = split(text, "\r\n", REMOVE_SPLITER); // 將組合語言分割成一行一行
ArrayEach(lines, strPrintln); // 印出以便觀察
printf("=================PASS1================\n");
for (i=0; i<lines->count; i++) { // 對於每一行
strReplace(lines->item[i], SPACE, ' ');
AsmCode *code = AsmCodeNew(lines->item[i]); // 建立指令物件
code->address = address; // 設定該行的位址
Op *op = HashTableGet(opTable, code->op); // 查詢運算碼
if (op != NULL) { // 如果查到
code->opCode = op->code; // 設定運算碼
code->type = op->type; // 設定型態
}
if (strlen(code->label)>0) // 如果有標記符號
HashTablePut(a->symTable, code->label, code); // 加入符號表中
ArrayAdd(a->codes, code); // 建構指令物件陣列 list
AsmCodePrintln(code); // 印出觀察
code->size = AsmCodeSize(code); // 計算指令大小
address += code->size; // 計算下一個指令位址
}
ArrayFree(lines, strFree); // 釋放記憶體
}
```
pass1 做了 絕對定址的動作和 為我們的 產生 目的碼
也就是
![](https://i.imgur.com/wjcrAHs.png)
這個地方
可以看到我們的 經過 pass1 後 我們的 組合語言 被展開成
```code
LD R1, B
```
address asm 指令型態 和 所使用 暫存器 r1 和最後的 相對定址 ..
```code
0000 LD R1, B L 0 (NULL)
```
# for 迴圈
我們直接看迴圈裏面可以看到
我們的 strReplace 去做去除 換行之類的動作
![](https://i.imgur.com/lHeNrqU.png)
接下來再進行我們的 AsmCode 我們這邊把我們剛剛處理完的
lines[i]取出來,也就是第一行
我們可以看到
函數 **AsmCodeNew**
幫我們把我們取出來的 該行數 進行初步字串處理
```c
AsmCode* AsmCodeNew(char *line) {
AsmCode* code = ObjNew(AsmCode,1);
char label[100]="", op[100]="", args[100]="", temp[100];
int count = sscanf(line, "%s %s %[^;]", label, op, args);
if (strTail(label, ":")) {
strTrim(temp, label, ":");
strcpy(label, temp);
} else {
strcpy(label, "");
sscanf(line, "%s %[^;]", op, args);
}
// printf("label=%s op=%s args=%s\n", code->label, op, args);
code->label = newStr(label);
code->op = newStr(op);
strTrim(temp, args, SPACE);
code->args = newStr(temp);
code->type = ' ';
code->opCode = OP_NULL;
// AsmCodePrintln(code);
return code;
}
```
處理完後我們的 code 在呼叫完 **AsmCodeNew** 返回 **AsmCode** 這個結構。
**AsmCode**
```c
typedef struct { // 指令物件
int address, opCode, size; // 包含位址、運算碼、
char *label, *op, *args, type; // 空間大小、op, 、標記、
char *objCode; // 參數、型態、目的碼
} AsmCode; // 等欄位
```
# hit hash table
我們看到這邊 可以看到我們去用我們分析完的 code-> op code
去 hit 我們的 opTable 代表我們去查表看我們的 cpu 支不支援我們的 opcode
```c
code->address = address; // 設定該行的位址
Op *op = HashTableGet(opTable, code->op); // 查詢運算碼
if (op != NULL) { // 如果查到
code->opCode = op->code; // 設定運算碼
code->type = op->type; // 設定型態
}
```
# insert symTable
假設我們的符號也就是 : 開頭的被我們的 **AsmCodeNew** 分析道 裡面的 code->label 不等於 0 ,
我們就把到目前我們對 code 的動作 全部 insert 到我們的symTable ,
```c
if (strlen(code->label)>0) // 如果有標記符號
HashTablePut(a->symTable, code->label, code); // 加入符號表中
```
# count code size and initialize address
這邊我們的 ArrayAdd 把我們的 剛剛對組合語言額外做的判斷
會導致我的的 指令 size 空間會被重新計算
所以我們要產生新的 Asmcode 加到我們一開始的 指令集 list
```c
ArrayAdd(a->codes, code); // 建構指令物件陣列 list
AsmCodePrintln(code); // 印出觀察
code->size = AsmCodeSize(code); // 計算指令大小
address += code->size; // 計算下一個指令位址
```
# AsmCodeSize
這邊可以看到我們在完成上述絕對定址後還需要對 我們的變數類進行分配記憶體空間,我們會在下面進行小分析。
```C
int AsmCodeSize(AsmCode *code)
{ // 計算指令的大小
switch (code->opCode)
{ // 根據運算碼 op
case OP_RESW: // 如果是RESW
return 4 * atoi(code->args); // 大小為 4*保留量
case OP_RESB: // 如果是RESB
return atoi(code->args); // 大小為 1*保留量
case OP_WORD: // 如果是WORD
return 4 * (strCountChar(code->args, ",") + 1); // 大小為 4*參數個數
case OP_BYTE: // 如果是BYTE
return strCountChar(code->args, ",") + 1; // 大小為1*參數個數
case OP_NULL: // 如果只是標記
return 0; // 大小為 0
default: // 其他情形 (指令)
return 4; // 大小為 4
}
}
```
# AsmPass2
這邊就要對我們的指令進行編碼動作 根據我們的 程式計數器 pc
來進行相對定址。
```c
void AsmPass2(Assembler *a) { // 組譯器的第二階段
printf("=============PASS2s==============\n");
int i;
for (i=0; i<a->codes->count; i++) { // 對每一個指令
AsmCode *code = a->codes->item[i];
AsmTranslateCode(a, code); // 進行編碼動作
// printf("ssssss\n"); // 輸入組合語言、輸出目的檔
AsmCodePrintln(code);
}
}
```
# AsmTranslateCode
這邊我們先針對 我們各個case 進行分析
```c
void AsmTranslateCode(Assembler *a, AsmCode *code) { // 指令的編碼函數
char p1[100], p2[100], p3[100], pt[100];
int ra=0, rb=0, rc=0, cx=0;
char cxCode[9]="00000000", objCode[100]="", args[100]="";
strcpy(args, code->args);
strReplace(args, ",", ' ');
printf("address now :%d\n" , code->address) ;
int pc = code->address + 4; // 提取後PC為位址+4
switch (code->type) { // 根據指令型態
case 'J' : // 處理 J 型指令
if (!strEqual(args, "")) {
AsmCode *labelCode = HashTableGet(a->symTable,args); // 取得符號位址
cx = labelCode->address - pc; // 計算 cx 欄位
sprintf(cxCode, "%8x", cx);
printf("address next:%d\n" , pc);
printf("labelCode address next:%d\n" , labelCode->address);
}
sprintf(objCode, "%2x%s", code->opCode, &cxCode[2]); // 編出目的碼(16進位)
//printf("%2x%s\n", code->opCode, &cxCode[2]);
break;
case 'L' :
sscanf(args, "R%d %s", &ra, p2);
if (strHead(p2, "[")) {
sscanf(p2, "[R%d+%s]", &rb, pt);
if (sscanf(pt, "R%d", &rc)<=0)
sscanf(pt, "%d", &cx);
} else if (sscanf(p2, "%d", &cx)>0) {
} else {
AsmCode *labelCode = HashTableGet(a->symTable, p2);
cx = labelCode->address - pc;
rb = 15; // R[15] is PC
}
sprintf(cxCode, "%8x", cx);
sprintf(objCode, "%2x%x%x%s", code->opCode, ra, rb, &cxCode[4]);
// printf("%s\n",cxCode); // 輸入組合語言、輸出目的檔
// printf("%shahha\n", objCode); // 輸入組合語言、輸出目的檔
break;
case 'A' : // 處理 A 型指令
sscanf(args, "%s %s %s", p1, p2, p3); // 取得參數
sscanf(p1, "R%d", &ra); // 取得ra暫存器代號
sscanf(p2, "R%d", &rb); // 取得rb暫存器代號
if (sscanf(p3, "R%d", &rc)<=0) // 取得rc暫存器代號
sscanf(p3, "%d", &cx); // 或者是 cx 參數
sprintf(cxCode, "%8x", cx);
sprintf(objCode, "%2x%x%x%x%s", code->opCode,ra,rb,rc,&cxCode[5]); // 編出目的碼(16進位)
break;
case 'D' : { // 處理是資料宣告
// 我們將資料宣告 RESW, RESB, WORD, BYTE 也視為一種指令,其形態為 D
char format4[]="%8x", format1[]="%2x", *format = format1;
switch (code->opCode) { // 如果是 RESW
case OP_RESW: // 或 RESB
case OP_RESB: //
memset(objCode, '0', code->size*2); // 目的碼為 0000….
objCode[code->size*2] = '\0';
break; // 如果是 WORD:
case OP_WORD:
format = format4; // 設定輸出格式為 %8x
case OP_BYTE: { // 如果是 BYTE : 輸出格式為 %2x
Array *array = split(args, " ", REMOVE_SPLITER); // 其目的碼為每個數字轉為16進位的結果
char *objPtr = objCode;
int i=0;
for (i=0; i<array->count; i++) {
char *item = array->item[i];
if (isdigit(item[0]))
sprintf(objPtr, format, atoi(item));
else {
AsmCode *itemCode = HashTableGet(a->symTable, item);
sprintf(objPtr, format, itemCode->address);
}
objPtr += strlen(objPtr);
}
ArrayFree(array, strFree);
break;
} // case OP_BYTE:
} // switch
break;
} // case 'D'
default:
strcpy(objCode, "");
break;
}
strReplace(objCode, " ", '0');
strToUpper(objCode);
code->objCode = newStr(objCode);
}
```
# J case
在處理 J型指令可以查看至
![](https://i.imgur.com/wBqRh4a.png)
可以得知是由 一個 op 配一個 常數 c
那麼當我們的 程式 進行指令擷取的時候我們的程式計數器因為是cpu0的架構 每一個指令均佔 4 byte 所以每次進行指令擷取 都會讓我們的程式計數器 位置 往上加 4 byte 。
以我們的 Jcase 來說 我們一開始會拿我們的 args 也就是 op code 後面的 R1 B
> args :R1 B
> address now :0
> 0000 LD R1, B L 0 00100000
>
由於我們的 J指令專門存放 JMP 所以可能只有實作符號類型跳轉?
暫存器 或 特殊符號 ,所以照這樣跑的話,我們最上面的 INPUT 的 ASM JMP 那一欄位就是只有 一個特殊符號 B 但是 B 的位置是 0014 所以 我們在 PASS1 的時候就已經計算過 在初始化 變數
b Address 現在就是在計算 我們目前程式執行到的程式計數器
pc 到 b Address 的 offset。
![](https://i.imgur.com/aGtAWT7.png)
```c
printf("all args :%s\n", args);
printf("address now :%d\n" , code->address) ;
int pc = code->address + 4; // 提取後PC為位址+4
switch (code->type) { // 根據指令型態
case 'J' : // 處理 J 型指令
if (!strEqual(args, "")) {
AsmCode *labelCode = HashTableGet(a->symTable,args); // 取得符號位址
cx = labelCode->address - pc; // 計算 cx 欄位
sprintf(cxCode, "%8x", cx);
printf("labelCode args:%s\n", args);
printf("address next:%d\n" , pc);
printf("labelCode address next:%d\n" , labelCode->address);
}
```
# L case
在處理 L指令可以查看至
![](https://i.imgur.com/wBqRh4a.png)
> all args :R1 B
address now :0
0000 LD R1, B L 0 00100000
all args :R1 A
address now :4
0004 ST R1, A L 1 01100000
>
在這邊可以看到我們的 指令已經被拆成R1 B
裡面幾個 狀況可以分成
1. ra rb cx 都有值
1. 只有 cx
1. cx 是 特殊標記符號
```C
case 'L':
sscanf(args, "R%d %s", &ra, p2);
if (strHead(p2, "["))
{
sscanf(p2, "[R%d+%s]", &rb, pt);
if (sscanf(pt, "R%d", &rc) <= 0)
sscanf(pt, "%d", &cx);
}
else if (sscanf(p2, "%d", &cx) > 0)
{
}
else
{
AsmCode *labelCode = HashTableGet(a->symTable, p2);
cx = labelCode->address - pc;
rb = 15; // R[15] is PC
}
sprintf(cxCode, "%8x", cx);
sprintf(objCode, "%2x%x%x%s", code->opCode, ra, rb, &cxCode[4]);
break;
```
# A case
在處理 A指令可以查看至
![](https://i.imgur.com/wBqRh4a.png)
這邊可以看到 我們可以直接把我們的 args 直接分配到我們的
ra rb rc cs暫存器 ,稍微小懷疑一下
![](https://i.imgur.com/ZcQayUB.png)
在 CPU0的架構 A指令 集 好像只有對 暫存器做比較所以沒有沒有變數之類的東西 也就是訪問符號表(?。
```c
case 'A': // 處理 A 型指令
sscanf(args, "%s %s %s", p1, p2, p3); // 取得參數
sscanf(p1, "R%d", &ra); // 取得ra暫存器代號
sscanf(p2, "R%d", &rb); // 取得rb暫存器代號
if (sscanf(p3, "R%d", &rc) <= 0) // 取得rc暫存器代號
sscanf(p3, "%d", &cx); // 或者是 cx 參數
sprintf(cxCode, "%8x", cx);
sprintf(objCode, "%2x%x%x%x%s", code->opCode, ra, rb, rc, &cxCode[5]); // 編出目的碼(16進位)
break;
```
# D case
在處理 D指令
// 我們將資料宣告 RESW, RESB, WORD, BYTE 也視為一種指令,其形態為 D
意味著我們是直接
```c
case 'D':
{ // 處理是資料宣告
// 我們將資料宣告 RESW, RESB, WORD, BYTE 也視為一種指令,其形態為 D
char format4[] = "%8x", format1[] = "%2x", *format = format1;
switch (code->opCode)
{ // 如果是 RESW
case OP_RESW: // 或 RESB
case OP_RESB: //
memset(objCode, '0', code->size * 2); // 目的碼為 0000….
objCode[code->size * 2] = '\0';
break; // 如果是 WORD:
case OP_WORD:
format = format4; // 設定輸出格式為 %8x
case OP_BYTE:
{ // 如果是 BYTE : 輸出格式為 %2x
Array *array = split(args, " ", REMOVE_SPLITER); // 其目的碼為每個數字轉為16進位的結果
char *objPtr = objCode;
int i = 0;
for (i = 0; i < array->count; i++)
{
char *item = array->item[i];
if (isdigit(item[0]))
sprintf(objPtr, format, atoi(item));
else
{
AsmCode *itemCode = HashTableGet(a->symTable, item);
sprintf(objPtr, format, itemCode->address);
}
objPtr += strlen(objPtr);
}
ArrayFree(array, strFree);
break;
} // case OP_BYTE:
} // switch
break;
} // case 'D'
```
## OP_RESW OP_RESB
![](https://i.imgur.com/rDyrc3w.png)
* RESB 保留所示數量的位元組,供資料區使用
* RESW 保留所示數量的字組,供資料區使用
```c
case OP_RESW: // 或 RESB
case OP_RESB: //
memset(objCode, '0', code->size * 2); // 目的碼為 0000….
objCode[code->size * 2] = '\0';
break; // 如果是 WORD:
```
## OP_RESW OP_RESB
這邊的話 ,我們在D case 有看到我們的
> char format4[] = "%8x", format1[] = "%2x", *format = format1;
>
程式進行到 OP_WORD or OP_BYTE的時候把格式切成 format4
然後在進行處理 我們的 args 可以看到我們的 裡面有
isdigit 可能是計算 到底有幾個
![](https://i.imgur.com/MYglyOm.png)
加了幾行註解 發現 他是對 後面那個 args 進行轉為 16進位 也就是
29 to 1d 這邊有看到 他也可以填入 符號表意味者可以指定 特殊符號進行宣告?
![](https://i.imgur.com/MTaQNsI.png)
可以發現確實可以這樣操作。
```C
case OP_WORD:
format = format4; // 設定輸出格式為 %8x
case OP_BYTE:
{ // 如果是 BYTE : 輸出格式為 %2x
Array *array = split(args, " ", REMOVE_SPLITER); // 其目的碼為每個數字轉為16進位的結果
char *objPtr = objCode;
int i = 0;
for (i = 0; i < array->count; i++)
{
char *item = array->item[i];
if (isdigit(item[0]))
sprintf(objPtr, format, atoi(item));
else
{
AsmCode *itemCode = HashTableGet(a->symTable, item);
sprintf(objPtr, format, itemCode->address);
}
objPtr += strlen(objPtr);
}
ArrayFree(array, strFree);
break;
} // case OP_BYTE:
```
# AsmSaveObjFile ...
下面的 函數就不加以討論可能大致上就是列印,然後儲存我們的目的碼或者是 計算我們的 varibale size 或 釋放記憶體
```c
void AsmSaveObjFile(Assembler *a, char *objFile) {
printf("==========Save to ObjFile:%s==========\n", objFile);
FILE *file = fopen(objFile, "wb");
int i;
for (i=0; i<a->codes->count; i++) {
AsmCode *code = a->codes->item[i];
char *objPtr = code->objCode;
while (*objPtr != '\0') {
int x;
sscanf(objPtr, "%2x", &x);
assert(x >= 0 && x < 256);
BYTE b = (BYTE) x;
fwrite(&b, sizeof(BYTE), 1, file);
objPtr += 2;
char bstr[3];
sprintf(bstr, "%2x", b);
strReplace(bstr, " ", '0');
strToUpper(bstr);
printf("%s", bstr);
}
}
printf("\n");
fclose(file);
}
int AsmCodePrintln(AsmCode *code) {
char label[100] = "", address[100], buffer[200];
if (strlen(code->label)>0)
sprintf(label, "%s:", code->label);
sprintf(address, "%4x", code->address);
strReplace(address, " ", '0');
sprintf(buffer, "%s %-8s %-4s %-14s %c %2x %s\n", address, label, code->op, code->args, code->type, code->opCode, code->objCode);
strToUpper(buffer);
printf(buffer);
}
AsmCode* AsmCodeNew(char *line) {
AsmCode* code = ObjNew(AsmCode,1);
char label[100]="", op[100]="", args[100]="", temp[100];
int count = sscanf(line, "%s %s %[^;]", label, op, args);
if (strTail(label, ":")) {
strTrim(temp, label, ":");
strcpy(label, temp);
} else {
strcpy(label, "");
sscanf(line, "%s %[^;]", op, args);
}
// printf("label=%s op=%s args=%s\n", code->label, op, args);
code->label = newStr(label);
code->op = newStr(op);
strTrim(temp, args, SPACE);
code->args = newStr(temp);
code->type = ' ';
code->opCode = OP_NULL;
// AsmCodePrintln(code);
return code;
}
void AsmCodeFree(AsmCode *code) {
freeMemory(code->label);
freeMemory(code->op);
freeMemory(code->args);
freeMemory(code->objCode);
freeMemory(code);
}
int AsmCodeSize(AsmCode *code) { // 計算指令的大小
switch (code->opCode) { // 根據運算碼 op
case OP_RESW : // 如果是RESW
return 4 * atoi(code->args); // 大小為 4*保留量
case OP_RESB : // 如果是RESB
return atoi(code->args); // 大小為 1*保留量
case OP_WORD : // 如果是WORD
return 4 * (strCountChar(code->args, ",")+1); // 大小為 4*參數個數
case OP_BYTE : // 如果是BYTE
return strCountChar(code->args, ",")+1; // 大小為1*參數個數
case OP_NULL : // 如果只是標記
return 0; // 大小為 0
default : // 其他情形 (指令)
return 4; // 大小為 4
}
}
```
# 最終拓展
# OUTPUT
```C
===============Assemble=============
LD R1, B
ST R1, A
CMP A,B
JMP B
RET
B: WORD 29
C: WORD 10
A: RESW C
=================PASS1================
0000 LD R1, B L 0 (NULL)
0004 ST R1, A L 1 (NULL)
0008 CMP A,B A 10 (NULL)
000C JMP B J 26 (NULL)
0010 RET J 2C (NULL)
0014 B: WORD 29 D F2 (NULL)
0018 C: WORD 10 D F2 (NULL)
001C A: RESW C D F0 (NULL)
===============SYMBOL TABLE=========
001C A: RESW C D F0 (NULL)
0014 B: WORD 29 D F2 (NULL)
0018 C: WORD 10 D F2 (NULL)
=============PASS2s==============
0000 LD R1, B L 0 00100000
0004 ST R1, A L 1 01100000
0008 CMP A,B A 10 10000000
000C JMP B J 26 26000004
0010 RET J 2C 2C000000
0014 B: WORD 29 D F2 0000001D
0018 C: WORD 10 D F2 0000000A
001C A: RESW C D F0
==========Save to ObjFile:Ex4_1.obj0==========
001000000110000010000000260000042C0000000000001D0000000A
```
一個組譯器就誕生了。。,這位老師還有 虛擬機 編譯器DEMO最近在來小分析一下。