2012-07-20 57 views
0

我有一本书叫Michael of Abrash的大会禅。据我所知,所有的代码清单都在x86汇编中。我下载了WinASM和MASM,所以我可以组装并链接这些列表。但是,这些列表并不适用 - 事实上,我根本无法让他们工作。例如,这里是首批上市代码:我救它作为listing1.asmWinASM和禅宗大会的问题


; 
; *** Listing 2-1 *** 
; 
; The precision Zen timer (PZTIMER.ASM) 
; 
; Uses the 8253 timer to time the performance of code that takes 
; less than about 54 milliseconds to execute, with a resolution 
; of better than 10 microseconds. 
; 
; By Michael Abrash 4/26/89 
; 
; Externally callable routines: 
; 
; ZTimerOn: Starts the Zen timer, with interrupts disabled. 
; 
; ZTimerOff: Stops the Zen timer, saves the timer count, 
; times the overhead code, and restores interrupts to the 
; state they were in when ZTimerOn was called. 
; 
; ZTimerReport: Prints the net time that passed between starting 
; and stopping the timer. 
; 
; Note: If longer than about 54 ms passes between ZTimerOn and 
; ZTimerOff calls, the timer turns over and the count is 
; inaccurate. When this happens, an error message is displayed 
; instead of a count. The long-period Zen timer should be used 
; in such cases. 
; 
; Note: Interrupts *MUST* be left off between calls to ZTimerOn 
; and ZTimerOff for accurate timing and for detection of 
; timer overflow. 
; 
; Note: These routines can introduce slight inaccuracies into the 
; system clock count for each code section timed even if 
; timer 0 doesn't overflow. If timer 0 does overflow, the 
; system clock can become slow by virtually any amount of 
; time, since the system clock can't advance while the 
; precison timer is timing. Consequently, it's a good idea 
; to reboot at the end of each timing session. (The 
; battery-backed clock, if any, is not affected by the Zen 
; timer.) 
; 
; All registers, and all flags except the interrupt flag, are 
; preserved by all routines. Interrupts are enabled and then disabled 
; by ZTimerOn, and are restored by ZTimerOff to the state they were 
; in when ZTimerOn was called. 
; 

Code segment word public 'CODE' 
    assume cs:Code, ds:nothing 
    public ZTimerOn, ZTimerOff, ZTimerReport 

; 
; Base address of the 8253 timer chip. 
; 
BASE_8253  equ 40h 
; 
; The address of the timer 0 count registers in the 8253. 
; 
TIMER_0_8253  equ BASE_8253 + 0 
; 
; The address of the mode register in the 8253. 
; 
MODE_8253  equ BASE_8253 + 3 
; 
; The address of Operation Command Word 3 in the 8259 Programmable 
; Interrupt Controller (PIC) (write only, and writable only when 
; bit 4 of the byte written to this address is 0 and bit 3 is 1). 
; 
OCW3   equ 20h 
; 
; The address of the Interrupt Request register in the 8259 PIC 
; (read only, and readable only when bit 1 of OCW3 = 1 and bit 0 
; of OCW3 = 0). 
; 
IRR   equ 20h 
; 
; Macro to emulate a POPF instruction in order to fix the bug in some 
; 80286 chips which allows interrupts to occur during a POPF even when 
; interrupts remain disabled. 
; 
MPOPF macro 
    local p1, p2 
    jmp short p2 
p1: iret   ;jump to pushed address & pop flags 
p2: push cs  ;construct far return address to 
    call p1  ; the next instruction 
    endm 

; 
; Macro to delay briefly to ensure that enough time has elapsed 
; between successive I/O accesses so that the device being accessed 
; can respond to both accesses even on a very fast PC. 
; 
DELAY macro 
    jmp $+2 
    jmp $+2 
    jmp $+2 
    endm 

OriginalFlags  db ? ;storage for upper byte of 
        ; FLAGS register when 
        ; ZTimerOn called 
TimedCount  dw ? ;timer 0 count when the timer 
        ; is stopped 
ReferenceCount  dw ? ;number of counts required to 
        ; execute timer overhead code 
OverflowFlag  db ? ;used to indicate whether the 
        ; timer overflowed during the 
        ; timing interval 
; 
; String printed to report results. 
; 
OutputStr label byte 
     db 0dh, 0ah, 'Timed count: ', 5 dup (?) 
ASCIICountEnd label byte 
     db ' microseconds', 0dh, 0ah 
     db '$' 
; 
; String printed to report timer overflow. 
; 
OverflowStr label byte 
    db 0dh, 0ah 
    db '****************************************************' 
    db 0dh, 0ah 
    db '* The timer overflowed, so the interval timed was *' 
    db 0dh, 0ah 
    db '* too long for the precision timer to measure.  *' 
    db 0dh, 0ah 
    db '* Please perform the timing test again with the *' 
    db 0dh, 0ah 
    db '* long-period timer.        *' 
    db 0dh, 0ah 
    db '****************************************************' 
    db 0dh, 0ah 
    db '$' 

;******************************************************************** 
;* Routine called to start timing.     * 
;******************************************************************** 

ZTimerOn proc near 

; 
; Save the context of the program being timed. 
; 
    push ax 
    pushf 
    pop ax   ;get flags so we can keep 
        ; interrupts off when leaving 
        ; this routine 
    mov cs:[OriginalFlags],ah ;remember the state of the 
        ; Interrupt flag 
    and ah,0fdh   ;set pushed interrupt flag 
        ; to 0 
    push ax 
; 
; Turn on interrupts, so the timer interrupt can occur if it's 
; pending. 
; 
    sti 
; 
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause 
; linear counting rather than count-by-two counting. Also 
; leaves the 8253 waiting for the initial timer 0 count to 
; be loaded. 
; 
    mov al,00110100b  ;mode 2 
    out MODE_8253,al 
; 
; Set the timer count to 0, so we know we won't get another 
; timer interrupt right away. 
; Note: this introduces an inaccuracy of up to 54 ms in the system 
; clock count each time it is executed. 
; 
    DELAY 
    sub al,al 
    out TIMER_0_8253,al  ;lsb 
    DELAY 
    out TIMER_0_8253,al  ;msb 
; 
; Wait before clearing interrupts to allow the interrupt generated 
; when switching from mode 3 to mode 2 to be recognized. The delay 
; must be at least 210 ns long to allow time for that interrupt to 
; occur. Here, 10 jumps are used for the delay to ensure that the 
; delay time will be more than long enough even on a very fast PC. 
; 
    rept 10 
    jmp $+2 
    endm 
; 
; Disable interrupts to get an accurate count. 
; 
    cli 
; 
; Set the timer count to 0 again to start the timing interval. 
; 
    mov al,00110100b  ;set up to load initial 
    out MODE_8253,al  ; timer count 
    DELAY 
    sub al,al 
    out TIMER_0_8253,al  ;load count lsb 
    DELAY 
    out TIMER_0_8253,al  ;load count msb 
; 
; Restore the context and return. 
; 
    MPOPF    ;keeps interrupts off 
    pop ax 
    ret 

ZTimerOn endp 

;******************************************************************** 
;* Routine called to stop timing and get count.    * 
;******************************************************************** 

ZTimerOff proc near 

; 
; Save the context of the program being timed. 
; 
    push ax 
    push cx 
    pushf 
; 
; Latch the count. 
; 
    mov al,00000000b  ;latch timer 0 
    out MODE_8253,al 
; 
; See if the timer has overflowed by checking the 8259 for a pending 
; timer interrupt. 
; 
    mov al,00001010b  ;OCW3, set up to read 
    out OCW3,al   ; Interrupt Request register 
    DELAY 
    in al,IRR   ;read Interrupt Request 
        ; register 
    and al,1   ;set AL to 1 if IRQ0 (the 
        ; timer interrupt) is pending 
    mov cs:[OverflowFlag],al ;store the timer overflow 
        ; status 
; 
; Allow interrupts to happen again. 
; 
    sti 
; 
; Read out the count we latched earlier. 
; 
    in al,TIMER_0_8253  ;least significant byte 
    DELAY 
    mov ah,al 
    in al,TIMER_0_8253  ;most significant byte 
    xchg ah,al 
    neg ax   ;convert from countdown 
        ; remaining to elapsed 
        ; count 
    mov cs:[TimedCount],ax 
; Time a zero-length code fragment, to get a reference for how 
; much overhead this routine has. Time it 16 times and average it, 
; for accuracy, rounding the result. 
; 
    mov cs:[ReferenceCount],0 
    mov cx,16 
    cli    ;interrupts off to allow a 
        ; precise reference count 
RefLoop: 
    call ReferenceZTimerOn 
    call ReferenceZTimerOff 
    loop RefLoop 
    sti 
    add cs:[ReferenceCount],8 ;total + (0.5 * 16) 
    mov cl,4 
    shr cs:[ReferenceCount],cl ;(total)/16 + 0.5 
; 
; Restore original interrupt state. 
; 
    pop ax   ;retrieve flags when called 
    mov ch,cs:[OriginalFlags] ;get back the original upper 
        ; byte of the FLAGS register 
    and ch,not 0fdh  ;only care about original 
        ; interrupt flag... 
    and ah,0fdh   ;...keep all other flags in 
        ; their current condition 
    or ah,ch   ;make flags word with original 
        ; interrupt flag 
    push ax   ;prepare flags to be popped 
; 
; Restore the context of the program being timed and return to it. 
; 
    MPOPF    ;restore the flags with the 
        ; original interrupt state 
    pop cx 
    pop ax 
    ret 

ZTimerOff endp 

; 
; Called by ZTimerOff to start timer for overhead measurements. 
; 

ReferenceZTimerOn proc near 
; 
; Save the context of the program being timed. 
; 
    push ax 
    pushf  ;interrupts are already off 
; 
; Set timer 0 of the 8253 to mode 2 (divide-by-N), to cause 
; linear counting rather than count-by-two counting. 
; 
    mov al,00110100b ;set up to load 
    out MODE_8253,al ; initial timer count 
    DELAY 
; 
; Set the timer count to 0. 
; 
    sub al,al 
    out TIMER_0_8253,al ;load count lsb 
    DELAY 
    out TIMER_0_8253,al ;load count msb 
; 
; Restore the context of the program being timed and return to it. 
; 
    MPOPF 
    pop ax 
    ret 

ReferenceZTimerOn endp 

; 
; Called by ZTimerOff to stop timer and add result to ReferenceCount 
; for overhead measurements. 
; 

ReferenceZTimerOff proc near 
; 
; Save the context of the program being timed. 
; 
    push ax 
    push cx 
    pushf 
; 
; Latch the count and read it. 
; 
    mov al,00000000b  ;latch timer 0 
    out MODE_8253,al 
    DELAY 
    in al,TIMER_0_8253  ;lsb 
    DELAY 
    mov ah,al 
    in al,TIMER_0_8253  ;msb 
    xchg ah,al 
    neg ax   ;convert from countdown 
        ; remaining to amount 
        ; counted down 
    add cs:[ReferenceCount],ax 
; 
; Restore the context of the program being timed and return to it. 
; 
    MPOPF 
    pop cx 
    pop ax 
    ret 

ReferenceZTimerOff endp 

;******************************************************************** 
;* Routine called to report timing results.    * 
;******************************************************************** 

ZTimerReport proc near 

    pushf 
    push ax 
    push bx 
    push cx 
    push dx 
    push si 
    push ds 
; 
    push cs ;DOS functions require that DS point 
    pop ds ; to text to be displayed on the screen 
    assume ds:Code 
; 
; Check for timer 0 overflow. 
; 
    cmp [OverflowFlag],0 
    jz PrintGoodCount 
    mov dx,offset OverflowStr 
    mov ah,9 
    int 21h 
    jmp short EndZTimerReport 
; 
; Convert net count to decimal ASCII in microseconds. 
; 
PrintGoodCount: 
    mov ax,[TimedCount] 
    sub ax,[ReferenceCount] 
    mov si,offset ASCIICountEnd - 1 
; 
; Convert count to microseconds by multiplying by .8381. 
; 
    mov dx,8381 
    mul dx 
    mov bx,10000 
    div bx  ;* .8381 = * 8381/10000 
; 
; Convert time in microseconds to 5 decimal ASCII digits. 
; 
    mov bx,10 
    mov cx,5 
CTSLoop: 
    sub dx,dx 
    div bx 
    add dl,'0' 
    mov [si],dl 
    dec si 
    loop CTSLoop 
; 
; Print the results. 
; 
    mov ah,9 
    mov dx,offset OutputStr 
    int 21h 
; 
EndZTimerReport: 
    pop ds 
    pop si 
    pop dx 
    pop cx 
    pop bx 
    pop ax 
    MPOPF 
    ret 

ZTimerReport endp 

Code ends 
    end 

。当我做出WinASM一个新项目,并选择使用标准EXE,其他的EXE或控制台应用程序作为我的项目类型,我得到下面的输出,当我做全力以赴(它是相同的,当我汇编和链接seperately):


C:\masm32\bin\ML /c /coff /Cp /nologo /I"C:\masm32\include" "C:\Users\Lincoln\Desktop\WinAsm\Projects\listing1.asm" 

Assembling: C:\Users\Lincoln\Desktop\WinAsm\Projects\listing1.asm 

C:\masm32\bin\Link @"C:\Users\Lincoln\Desktop\WinAsm\Projects\link.war" 

Microsoft (R) Incremental Linker Version 5.12.8078 
Copyright (C) Microsoft Corp 1992-1998. All rights reserved. 

/SUBSYSTEM:WINDOWS /RELEASE /VERSION:4.0 "/LIBPATH:C:\masm32\lib" "C:\Users\Lincoln\Desktop\WinAsm\Projects\listing1.obj" "/OUT:C:\Users\Lincoln\Desktop\WinAsm\Projects\listing1.exe" 
listing1.obj : fatal error LNK1190: invalid fixup found, type 0x0001 

Make finished. 1 error(s) occured. 

当我组装,并将其链接作为一个DOS项目,我得到下面的输出:


C:\masm32\bin\ML /c /I"C:\masm32\include" "C:\Users\Lincoln\Desktop\WinAsm\Projects\listing1.asm" 

Microsoft (R) Macro Assembler Version 6.14.8444 
Copyright (C) Microsoft Corp 1981-1997. All rights reserved. 

Assembling: C:\Users\Lincoln\Desktop\WinAsm\Projects\listing1.asm 

C:\masm32\bin\Link16 @"C:\Users\Lincoln\Desktop\WinAsm\Projects\link.war" 


Microsoft (R) Segmented Executable Linker Version 5.60.339 Dec 5 1994 
Copyright (C) Microsoft Corp 1984-1993. All rights reserved. 

Object Modules [.obj]: C:\Users\Lincoln\Desktop\WinAsm\Projects\listing1.objj 
LINK : warning L4021: no stack segment 
LINK : warning L4038: program has no starting address 

Make finished. 2 error(s) occured. 

我不知道是什么错误意味着,或者它为什么引起的。这本书“大会之禅”已经有二十多年的历史,所以它的设计和编写是为了在一个稍微不同的处理器上运行,但我的印象是大多数处理器完全向后兼容,所以我认为这不是一个问题。无论如何,有没有人知道是什么导致这些错误信息?很明显,他们是链接器错误,它的组装很好,所以这给了我一个想法,即链接器MASM带来了一些不太正确的东西 - 我应该下载一个新的还是什么?

顺便说一句,我在一个32位的操作系统,而不是64位的一个,和我运行Windows 7.如果您有关于我的电脑有任何问题,我使用要做到这一点,随意问。

+0

你试过nasm吗?很好的书btw,理解它出来时已经过时了。最重要的不是关于编程,而是关于8088或8086的细微差别,而是如何“思考”这个问题,然后再次猜测自己,尝试一些疯狂的事情并对它们进行计时(并且明白你可以用计时器来犯错误所以即使是第二次猜测)。 – 2012-07-20 02:50:06

+0

我没有,但我会尝试NASM。是的,即使我无法获得列表,我也很期待阅读这本书。 – 2012-07-20 02:59:08

+0

我试图与NASM组装,并得到一些奇怪的错误;如果你熟悉它,也许你提供帮助,dwelch:http://stackoverflow.com/questions/11572307/nasm-error-parsing-instruction-expected – 2012-07-20 03:10:15

回答

2

的问题是,你的汇编文件不包含一个完整的程序,但只被设计单独的程序,以从你的主程序调用。你可以将这个程序集文件编译成一个目标文件,然后你可以链接到你的主程序。

链接器抱怨了两件事:首先,你的程序没有声明一个堆栈段 - 参见here的随机例子,说明如何做到这一点(请参阅segment para stack指令)。其次,你的程序没有起始地址 - 你通常会使用start:标签来提供。在C中的近似比喻将不具有main()函数。

我建议找一个简单的程序模板,让你开始 - “禅”假设你已经知道x86汇编编程的基础知识。例如,here是16位DOS的“Hello World”版本。

顺便说一句,您发布的程序将仅在16位MS-DOS环境中的PC上运行时,因为它们直接访问8253计时器芯片工作。

+0

非常感谢,很好地回答了我的问题。 – 2012-07-20 14:06:40

+0

@LincolnB很高兴听到它!顺便说一句,建议您应该通过点击“打勾”图标“接受”最有用的答案 - 更多信息:http://stackoverflow.com/faq/#howtoask – 2012-07-23 08:35:04