在 16 位元 C 語言 (Small/Medium Model) 中,程式碼與資料分散於不同段 (CS, DS, ES, SS)。有時需要在 C 代碼中獲取這些段值 (如建立遠端指標、呼叫 DOS 中斷需段:偏移、除錯顯示)。GETSEG.ASM 提供一個 near 函式 getseg,供 C 以 extern unsigned getseg(unsigned *, ...) 呼叫,將 CS, DS, ES, SS, SI, DI, SP, FLAGS 寫入呼叫端提供的緩衝區。程式展示了組合語言存取呼叫端堆疊參數、操作段暫存器、處理 C 呼叫慣例的標準技巧。
C 宣告:
void getseg(unsigned *cs, unsigned *ds, unsigned *es, unsigned *ss, unsigned *si, unsigned *di, unsigned *sp, unsigned *flags);Small Model 下,參數以 **近指標** (16 位元偏移) 經堆疊 **右至左** 推入:
BP+4 : &cs BP+6 : &ds BP+8 : &es BP+10 : &ss BP+12 : &si BP+14 : &di BP+16 : &sp BP+18 : &flags函式需保存
BP, SI, DI, DS, ES, SS(C 要求保存),AX, BX, CX, DX可自由使用。核心實作邏輯
1. 取得段暫存器值
mov ax, cs ; 程式碼段 mov bx, ds ; 資料段 mov cx, es ; 附加段 mov dx, ss ; 堆疊段直接讀取當前段暫存器。注意:此函式編譯為獨立
PROG區段,連結時與 C 程式碼合併,故CS為程式碼段,DS為 C 的資料段 (DGROUP)。2. 經由堆疊參數指標寫回呼叫端緩衝區
uret: mov bp, sp mov si, [bp+4] ; 取得 &cs 指標 mov [si], ax ; 寫入 *cs = CS mov si, [bp+6] mov [si], bx ; *ds = DS mov si, [bp+8] mov [si], cx ; *es = ES mov si, [bp+10] mov [si], dx ; *ss = SS mov si, [bp+12] mov [si], si ; *si = SI (註: 此處 SI 已被覆寫,實際應先保存原 SI) mov si, [bp+14] mov [si], di ; *di = DI mov si, [bp+16] mov [si], sp ; *sp = SP mov al, 0 lahf ; AH = FLAGS 低位元組 mov si, [bp+18] mov [si], ax ; *flags = FLAGS (僅低 8 位? 實際 AH=完整 FLAGS 低位元組) mov ax, 0 ; 回傳 0 (成功) quit: pop bp ret關鍵問題:寫入
*si時,SI已被用作指標讀取參數,原始SI值已丟失。正確做法應在開頭PUSH SI保存,或在寫入前從堆疊框架恢復。此為原始碼一個微妙 Bug。3. FLAGS 取得技巧
mov al, 0 lahf ; Load AH from Flags (將 FLAGS 低位元組載入 AH) ; AH 現在包含 SF, ZF, AF, PF, CF 等旗標
LAHF將旗標暫存器低 8 位 (SF:ZF:0:AF:0:PF:1:CF) 載入AH,配合AL=0形成 16 位元值存入*flags。若需完整 16 位旗標,需PUSHF / POP AX。完整原始碼
pgroup group prog
prog segment byte public 'PROG'
assume cs:pgroup
public getseg
getseg proc near
push bp
mov bp,sp
mov si,[bp + 04]
mov ax,[si]
mov si,[bp + 06]
mov bx, [si]
mov si,[bp + 08]
mov cx, [si]
mov si,[bp + 10]
mov dx,[si]
;==============================================================================
mov ax, cs
mov bx, ds
mov cx, es
mov dx, ss
;==============================================================================
uret: mov bp,sp
mov si,[bp + 04]
mov [si],ax
mov si, [bp + 06]
mov [si], bx
mov si, [bp + 08]
mov [si], cx
mov si, [bp + 10]
mov [si], dx
mov si, [bp + 12]
mov [si], si
mov si, [bp + 14]
mov [si], di
mov si, [bp + 16]
mov [si], sp
mov al, 00
lahf
mov si, [bp + 18]
mov [si], ax
mov ax,0
quit: pop bp
ret
getseg endp
prog ends
end教學價值與修正建議
PUSH BP / MOV BP,SP 建立框架,[BP+n] 存取參數,標準 C 近呼叫慣例。MOV AX, CS 等指令在組合語言中直接讀取段值 (C 中無法直接存取)。MOV SI, [BP+n] / MOV [SI], REG) 寫回呼叫端變數。SI, DI, AX, BX, CX, DX, BP,若遵循 C 慣例需保存 SI, DI, BP, DS, ES, SS。原碼僅保存 BP,SI, DI 被破壞 — 實際連結可能導致呼叫端錯誤。getseg proc near
push bp
push si
push di
push ds
push es
mov bp, sp
... 邏輯 ...
pop es
pop ds
pop di
pop si
pop bp
ret
getseg endp
INT 21h 要求 ES:BX 遠端指標,可先 getseg(&es_val, ...) 取得 ES 段值。DWORD PTR = (seg<<16) | offset)。FREESPACE (ID 13300) 類似工具配合,獲取段值後計算記憶體佈局。GETSEG 雖小,卻精準示範了「組合語言為高階語言提供底層硬體存取」的經典協作模式。理解其堆疊參數佈局、段暫存器語義、指標解參考寫入、暫存器保存責任,是掌握 16 位元混合語言開發的必修課。