- •Table of Contents
- •Foreword
- •Do Not Pass GO
- •Counting in Martian
- •Octal: How the Grinch Stole Eight and Nine
- •Hexadecimal: Solving the Digit Shortage
- •From Hex to Decimal and from Decimal to Hex
- •Arithmetic in Hex
- •Binary
- •Hexadecimal as Shorthand for Binary
- •Switches, Transistors, and Memory
- •The Shop Foreman and the Assembly Line
- •The Box That Follows a Plan
- •DOS and DOS files
- •Compilers and Assemblers
- •The Assembly Language Development Process
- •DEBUG and How to Use It
- •Chapter 5: NASM-IDE: A Place to Stand Give me a lever long enough, and a place to stand, and I will move the Earth.
- •NASM-IDE's Place to Stand
- •Using NASM-IDE's Tools
- •NASM-IDE's Editor in Detail
- •Other NASM-IDE Features
- •The Nature of Segments
- •16-Bit and 32-Bit Registers
- •The Three Major Assembly Programming Models
- •Reading and Changing Registers with DEBUG
- •Assembling and Executing Machine Instructions with DEBUG
- •Machine Instructions and Their Operands
- •Reading and Using an Assembly Language Reference
- •Rally Round the Flags, Boys!
- •Using Type Specifiers
- •The Bones of an Assembly Language Program
- •Assembling and Running EAT.ASM
- •One Program, Three Segments
- •Last In, First Out via the Stack
- •Using DOS Services through INT
- •Boxes within Boxes
- •Using BIOS Services
- •Building External Libraries of Procedures
- •Creating and Using Macros
- •Bits Is Bits (and Bytes Is Bits)
- •Shifting Bits
- •Flags, Tests, and Branches
- •Assembly Odds 'n Ends
- •The Notion of an Assembly Language String
- •REP STOSW, the Software Machine Gun
- •The Semiautomatic Weapon: STOSW without REP
- •Storing Data to Discontinuous Strings
- •Chapter 12: The Programmer's View of Linux Tools and Skills to Help You Write Assembly Code under a True 32-Bit OS
- •Prerequisites-Yukkh!
- •NASM for Linux
- •What's GNU?
- •The make Utility and Dependencies
- •Using the GNU Debugger
- •Your Work Strategy
- •Genuflecting to the C Culture
- •A Framework to Build On
- •The Perks of Protected Mode
- •Characters Out
- •Characters In
- •Be a Time Lord
- •Generating Random Numbers
- •Accessing Command-Line Arguments
- •Simple File I/O
- •Conclusion: Not the End, But Only the Beginning
- •Where to Now?
- •Stepping off Square One
- •Notes on the Instruction Set Reference
- •AAA Adjust AL after BCD Addition
- •ADC Arithmetic Addition with Carry
- •ADD Arithmetic Addition
- •AND Logical AND
- •BT Bit Test (386+)
- •CALL Call Procedure
- •CLC Clear Carry Flag (CF)
- •CLD Clear Direction Flag (DF)
- •CMP Arithmetic Comparison
- •DEC Decrement Operand
- •IMUL Signed Integer Multiplication
- •INC Increment Operand
- •INT Software Interrupt
- •IRET Return from Interrupt
- •J? Jump on Condition
- •JMP Unconditional Jump
- •LEA Load Effective Address
- •MOV Move (Copy) Right Operand into Left Operand
- •NOP No Operation
- •NOT Logical NOT (One's Complement)
- •OR Logical OR
- •POP Pop Top of Stack into Operand
- •POPA Pop All 16-Bit Registers (286+)
- •POPF Pop Top of Stack into Flags
- •POPFD Pop Top of Stack into EFlags (386+)
- •PUSH Push Operand onto Top of Stack
- •PUSHA Push All 16-Bit GP Registers (286+)
- •PUSHAD Push All 32-Bit GP Registers (386+)
- •PUSHF Push 16-Bit Flags onto Stack
- •PUSHFD Push 32-Bit EFlags onto Stack (386+)
- •RET Return from Procedure
- •ROL Rotate Left
- •ROR Rotate Right
- •SBB Arithmetic Subtraction with Borrow
- •SHL Shift Left
- •SHR Shift Right
- •STC Set Carry Flag (CF)
- •STD Set Direction Flag (DF)
- •STOS Store String
- •SUB Arithmetic Subtraction
- •XCHG Exchange Operands
- •XOR Exclusive Or
- •Appendix C: Web URLs for Assembly Programmers
- •Appendix D: Segment Register Assumptions
- •Appendix E: What's on the CD-ROM?
- •Index
- •List of Figures
- •List of Tables
Be a Time Lord
The standard C libraries contain a pretty substantial group of functions that manipulate dates and times. Although these functions were originally designed to handle date values generated by the real-time clock in ancient AT&T minicomputer hardware, they have by now become a standard interface to any operating system's real-time clock support. People who program in C under DOS or for Windows use the very same group of functions, and they work more or less the same way irrespective of what platform you're working with.
By understanding how to call these functions as assembly language procedures, you'll be able to read the current date, express time and date values in numerous formats, apply timestamps to files, and do many other useful things.
Let's take a look at how it works.
The C Library's Time Machine
Somewhere deep inside the standard C library, there is a block of code that, when invoked, looks at the real-time clock in the computer, reads the current date and time, and translates that into a standard, 32-bit unsigned integer value. This value is the number of seconds that have passed in the "Unix Epoch," which began on January 1, 1970, 00:00:00 universal time. Every second that passes adds one to this value. When you read the current time or date via the C library, what you'll retrieve is the current value of this number.
The number is called time_t. The time_t value is currently in the high 900,000,000s, and will flip to 10 digits (1 billion seconds since January 1, 1970) on September 9, 2001, at 7:46:40 A.M. UTC. This isn't a Y2K-style hazard in the immediate future, since even a signed 32-bit integer can express a quantity over 2 billion, and an unsigned 32-bit integer can express over 4 billion. Furthermore, a properly implemented C library doesn't assume that this is a 32-bit quantity at all. So, when the whole thing flips in the year 2069, we'll already be using at least 64-bit values for everything and the whole problem will be put off for another 292 billion years or so. If we haven't fixed it once and for all by then, we'll deserve to go down in the Cosmic Crunch that cosmologists are predicting.
A time_t value is just an arbitrary seconds count and doesn't tell you much on its own, though it can be useful for calculating elapsed times in seconds. A second standard data type implemented by the standard C library is much more useful. A tm structure (which is often called a struct, and which is what Pascal people would call a record) is a grouping of nine 32-bit values that express the current time and date in separately useful chunks, as summarized in Table 13.3. Note that although a struct (or record) is nominally a grouping of unlike values, in the current x86 Linux implementation, a tm value is more like an array or a data table, because all nine elements are the same size, which is 32 bits, or 4 bytes. I've described it that way in Table 13.3, by including a value that is the offset from the beginning of the structure for each element in the structure. This allows you to use a pointer to the beginning of the structure and an offset from the beginning to close in on any given element of the structure.
Table 13.3: The Values Contained in the tm Structure
|
|
|
|
|
|
|
|
OFFSET IN BYTES |
|
C LIBRARY NAME |
|
DEFINITION |
|
|
|
|
|
|
|
|
|
0 |
|
tm_sec |
|
Seconds after the minute, from 0 |
|
|
|
|
|
|
|
|
|
4 |
|
tm_min |
|
Minutes after the hour, from 0 |
|
|
|
|
|
|
|
|
|
8 |
|
tm_hour |
|
Hour of the day, from 0 |
|
|
|
|
|
|
|
|
|
12 |
|
tm_mday |
|
Day of the month, from 1 |
|
|
|
|
|
|
|
|
|
16 |
|
tm_mon |
|
Month of the year, from 0 |
|
|
|
|
|
|
|
|
|
20 |
|
tm_year |
|
Year since 1900, from 0 |
|
|
|
|
|
|
|
|
|
24 |
|
tm_wday |
|
Days since Sunday, from 0 |
|
|
|
|
|
|
|
|
|
28 |
|
tm_yday |
|
Day of the year, from 0 |
|
|
|
|
|
|
|
|
|
32 |
|
tm_isdst |
|
Daylight Savings Time flag |
|
|
|
|
|
|
|
|
There are C library functions that convert time_t values to tm values and back. I cover a few of them in this book, but they're all pretty straightforward, and once you've thoroughly internalized the C calling conventions, you should be able to work out an assembly calling mechanism for any of them.
Fetching time_t Values from the System Clock
Any single second of time (at least those seconds after January 1, 1970) can be represented as a 32-bit unsigned integer in the Unix system.
Fetching the value for the current time is done by calling the time function:
push dword 0 |
; Push a 32-bit |
null pointer to stack, since |
|
; we don't need |
a buffer. Time value is |
call time |
; returned in eax. |
|
; Returns calendar time in eax |
||
add esp, byte 4 |
; Clean up stack after call |
mov [oldtime],eax ; Save time value in memory variable
The time function can potentially return the time value in two places: In EAX, or in a buffer that you allocate somewhere. To have time place the value in a buffer, you pass it a pointer to that buffer on the stack. If you don't want to store the time value in a buffer, you must still hand it a null pointer on the stack. That's why we push a 0 value in the preceding code; 0 is the value of a null pointer.
No other arguments need to be passed to time. On return, you'll have the current time value (what Unixoids call time_t) in EAX. That's all there is to it.
Converting a time_t Value to a Formatted String
At this writing, time_t is up to about 950,000,000. (Scary to think that that many seconds have passed since the middle of my senior year in high school-which is precisely the time I first learned about computers!) By itself, time_t doesn't tell you a great deal. The C library contains a function that will return a pointer to a formatted string representation of a given time_t. This is the ctime function. It returns a pointer to a string buried somewhere in the runtime library. This string has the following format:
Thu Dec 2 13:59:20 1999
The first field is a three-character code for the day of the week, followed by a three-character code for the month and a two-space field for the day of the month. The time follows, in 24-hour format, and the year brings up the rear. For good measure (though it is sometimes a nuisance), the string is terminated by a newline.
Here's how you use ctime:
push dword |
oldtime ; Push *address* of calendar time value |
||
call ctime |
|
; |
Returns pointer to ASCII time string in eax |
add esp, byte 4 |
; |
Clean up stack after call |
This looks pretty conventional, but there is something here that you must notice, as it's a little unconventional: You pass ctime the address of a time_t value, not the value itself! You're used to passing 32-bit integer values by pushing the values themselves onto the stack, say, for display by printf. Not so here. A time_t value is currently, under Linux, represented as a 4-byte integer, but there is no promise that it will always be thus. So, to keep its options open (and to ensure that Unix can be used for thousands or even millions of years to come, egad), the C library requires a pointer to the current time. Maybe in a thousand years it'll be a quad word . . .
who's to say?
So you push a pointer to the time_t value that you want to represent as a string, and then call ctime. What ctime returns is a pointer to the string, which it keeps somewhere inside the library. You can use that pointer to display the string on the screen via printf or to write it to a text file.
Generating Separate Local Time Values
The C library also gives you a function to break out the various components of the date and time into separate values, so you can use them separately or in various combinations. This function is localtime, and given a time_t value, it will break out the date and time into the fields of a tm structure described in Table 13.3. Here's the code to call it:
push dword oldtime |
; Push address of |
calendar time value |
||
call localtime |
; |
Returns pointer |
to static |
time structure in eax |
add esp, byte 4 |
; |
Clean up stack after call |
|
Here, oldtime is a time_t value. Given this value, localtime returns in EAX-much in the fashion of ctime-a pointer to a tm structure within the runtime library somewhere. By using this pointer as a base address, you can access the fields in the structure by using a constant displacement from the base (here, shown as stored in EAX):
mov |
edx, dword [eax+20] ; Year value is 20 bytes offset into tm |
|||
push edx |
; Push |
value onto |
the stack |
|
push dword yrmsg |
; Push |
address of |
the base string |
|
call printf |
; Display string and year value with printf |
|||
add |
esp, byte 8 |
; Clean up the stack |
By using the displacements shown in Table 13.3, you can access all the other components of the time and the date in the tm structure, stored as 32-bit integer values.
Uninitialized Storage and [.bss]
To newcomers, the difference between the [.data] and [.bss] sections of the program may be obscure. Both are used for holding variables . . . so, what's the deal? Is it (like many other things in computing) just more, . . . um, . .
. bss?
Not really. Again, the difference is more a matter of convention than anything else. The [.data] section was intended to contain initialized data; that is, variables that you provide with initial values. Most of the time, these will be base strings for data display containing prompts and other string data that doesn't change during the course of a program's execution. Sometimes you'll store count values there that define the number of lines in an output report, and so on. These values are much like values defined as CONSTANT in Pascal. They're defined at compile time and are not supposed to change.
In assembly, of course, you can change them if you want. But for variables that begin without values (that is, are uninitialized) which are given values over the course of a program's execution (which is the way most high-level language programmers think of variables), you should probably allocate them in the [.bss] section.
There are two groups of data-definition pseudoinstructions that I've used informally all along. They are what I call the defines and the reserves. The define pseudoinstructions give a name, a size, and a value to a data item. The reserves only give a name and a size. Here are some examples:
rowcount dd 6 |
|
|
|
fileop |
db 'w',0 |
|
|
timemsg |
db "Hey, what time is it? It's %s",10,0 |
||
timediff resd 1 |
; Reserve 1 |
integer (4 bytes) for time difference |
|
timestr |
resb 40 |
; Reserve 40 bytes for time string |
|
tmcopy |
resd 9 |
; Reserve 9 |
integer fields for time struct tm |
The first group are the defines. The ones you'll use most often are DD (define double) and DB (define byte). The DB pseudoinstruction is unique in that it allows you to define character arrays very easily, and it is generally used for string constants. For more advanced work, NASM provides you with DW (define word) for 16-bit quantities, DQ (define quad word) for 64-byte quantities, and DT (define ten-byte) for 80-bit quantities. These larger types are used for floating-point arithmetic, which I won't be covering in this book.
The second group are reserves. They all begin with "RES," followed by the code that indicates the size of the item to be reserved. NASM defines RESB, RESW, RESD, RESQ, and REST for bytes, words, doubles, quads, and 10-bytes. The reserves allow you to allocate arrays of any type, by specifying an integer constant after the pseudoinstruction. RESB 40 allocates 40 bytes, and RESD 9 allocates 9 doubles (32-bit quantities) all in a contiguous array.
Making a Copy of clib's tm Struct with MOVSD
It's sometimes handy to be able to keep a separate copy of a tm structure, especially if you're working with several date/time values at once. So, after you use localtime to fill the C library's hidden tm structure with date/time values, you can copy that structure to a structure allocated in the [.bss] section of your program.
Doing such a copy is a straightforward use of the REP MOVSD (Repeat Move String Double) instruction. MOVSD is an almost magical thing: Once you set up pointers to the data area you want to copy, and the place you want to copy it to, you store the size of the area in ECX and let REP MOVSD do the rest. In one operation it will copy an entire buffer from one place in memory to another.
To use REP MOVSD, you place the address of the source data-that is, the data to be copied-into ESI. You move the address of the destination location-where the data is to be placed-in EDI. The number of items to be moved is placed in ECX. You make sure the Direction flag is cleared (for more on this, see Chapter 11) and then execute REP MOVSD:
mov esi, eax |
; |
Copy address of static tm from eax to esi |
||
mov edi, dword tmcopy ; |
Put the address of the local tm copy in edi |
|||
mov ecx,9 |
; A tm |
struct is |
9 dwords in size under Linux |
|
cld |
; Clear df to 0 so we move up-memory |
|||
rep movsd |
; Copy |
static tm |
struct to local copy in .bss |
Here, we're moving the C library's tm structure to a buffer allocated in the [.bss] section of the program. The tm structure is 9 double words-36 bytes-in size. So, we have to reserve that much space and give it a name:
tmcopy |
resd 9 |
; Reserve 9 integer fields for time struct tm |
The preceding code assumes that the address of the C library's already-filled tm structure is in EAX, and that a tm structure tmcopy has been allocated. Once executed, it will copy all of the tm data from its hidey-hole inside the C runtime library to your freshly allocated buffer.
The REP prefix puts MOVSD in automatic-rifle mode, as I explained in Chapter 11. That is, MOVSD will keep moving data from the address in ESI to the address in EDI, counting ECX down by one with each move, until ECX goes to zero. Then it stops.
One oft-made mistake is forgetting that the count in ECX is the count of data items to be moved, not the number of bytes to be moved! By virtue of the D on the end of its mnemonic, MOVSD moves double words, and the value you place in ECX must be the number of 4-byte items to be moved. So, in moving 9 double words, MOVSD actually transports 36 bytes from one location to another-but you're counting doubles here, not bytes.
The following program knits all of these snippets together into a demo of the major Unix time features. There are many more time functions to be studied in the C library, and with what you now know about C function calls, you should be able to work any of them out.
; Source name |
: TIMETEST.ASM |
; Executable name : TIMETEST |
|
; Version |
: 1.0 |
; Created date |
: 12/2/1999 |
; Last update |
: 12/3/1999 |
; Author |
: Jeff Duntemann |
; Description |
: A demo of time-related functions for Linux, using NASM 0 |
; |
|
;Build using these commands:
;nasm -f elf timetest.asm
;gcc timetest.o -o timetest
[SECTION .text] |
; Section containing code |
extern ctime extern getchar extern printf
extern localtime extern time
global main |
; Required so linker can find entry point |
main: |
; Set up stack frame for debugger |
push ebp |
|
mov ebp,esp |
; Program must preserve ebp, ebx, esi, & edi |
push ebx |
|
push esi |
|
push edi |
|
;;;Everything before this is boilerplate; use it for all ordinary apps!
;;;Generate a time_t calendar time value with clib's time function========= push dword 0 ; Push a 32-bit null pointer to stack, since
;we don't need a buffer. Time value is
;returned in eax.
call time |
; Returns calendar time in eax |
add esp, byte 4 |
; Clean up stack after call |
mov [oldtime],eax |
; Save time value in memory variable |
;;; Generate a string summary of local time with clib's ctime function======
push dword oldtime |
; Push address of calendar time value |
call ctime |
; Returns pointer to ASCII time string in eax |
add esp, byte 4 |
; Clean up stack after call |
push eax |
; Push pointer to ASCII time string on stack |
push dword timemsg |
; Push pointer to base message text string |
call printf |
; Merge and display the two strings |
add esp, byte 8 |
; Clean up stack after call |
;;; Generate local time values into clib's static tm struct=================
push dword oldtime |
; |
Push address of calendar time value |
call localtime |
; Returns pointer to static time structure in eax |
|
add esp, byte 4 |
; |
Clean up stack after call |
;;; Make a local copy of clib's static tm struct============================
mov esi, eax |
; |
Copy address of static tm from eax to esi |
|||||
mov edi, dword tmcopy ; |
Put the address of the local tm copy in edi |
||||||
mov ecx,9 |
; |
A tm |
struct is |
9 |
dwords in size under Linux |
||
cld |
; Clear df to |
0 so |
we move |
up-memory |
|||
rep movsd |
; |
Copy |
static |
tm |
struct to |
local copy in .bss |
;;; Display one of the fields in the tm structure===========================
mov edx, dword [tmcopy+20] ; Year value is 20 bytes offset into tm
push edx |
; Push |
value onto |
the stack |
|
push dword yrmsg |
; |
Push |
address of |
the base string |
call printf |
; |
Display string and year value with printf |
||
add esp, byte 8 |
; Clean up the stack |
;;;Wait a few seconds for user to press Enter so we have a time difference=
call getchar
;;;Calculating seconds passed since program began running with difftime==== push dword 0 ; Push null ptr; we'll take value in eax
call time |
; Get current time value; return in eax |
||||
add esp, byte 4 |
; Clean up |
the stack |
|||
mov [newtime],eax |
; Save |
new |
time value |
||
sub |
eax,[oldtime] |
; |
Calculate time difference value |
||
mov |
[timediff],eax |
; |
Save |
time difference value |
push dword [timediff] ; Push difference in seconds onto the stack
push dword elapsed |
; Push addr. of elapsed time message string |
|
call printf |
; Display elapsed time |
|
add |
esp, byte 8 |
; Clean up the stack |
;;; Everything after this is boilerplate; use it for all ordinary apps! |
||
pop |
edi |
; Restore saved registers |
pop esi |
|
|
pop ebx |
; Destroy stack frame before returning |
|
mov |
esp,ebp |
|
pop ebp |
; Return control to Linux |
|
ret |
|
|
[SECTION .data] |
; Section containing initialized data |
|
timemsg |
db "Hey, what time is it? It's %s",10,0 |
|
yrmsg |
db "The year is 19%d.",10,0 |
|
elapsed |
db "A total of %d seconds has elapsed since program began running.", |
|
[SECTION .bss] |
; Section containing uninitialized data |
|
oldtime |
resd 1 |
; Reserve 3 integers (doubles) for time values |
newtime |
resd 1 |
|
timediff resd 1 |
; Reserve 40 bytes for time string |
|
timestr |
resb 40 |
|
tmcopy |
resd 9 |
; Reserve 9 integer fields for time struct tm |