Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Assembly Language Step by Step Programming with DOS and Linux 2nd Ed 2000.pdf
Скачиваний:
156
Добавлен:
17.08.2013
Размер:
4.44 Mб
Скачать

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