Ground Up
Jonathan Bartlett , Dominick Bruno
Процессор состоит из следующих основных элементов :
1 Program Counter
2 Instruction Decoder
3 Data bus
4 General-purpose registers
5 Arithmetic and logic unit
Счетчик хранит адрес инструкции , которая будет выполняться в следующем цикле .
Инструкция попадает в декодкр и состоит из 2 частей - собственно инструкции и адреса .
Получив адрес , процессор обращается к памяти через шину .
В процессоре имеются 2 группы регистров :
1. general registers
2. special-purpose registers
После этого полученные данные передаются в логически-арифметический модуль,обрабатываются
и результат обработки передаётся назад в память .
Компьютерная память - это пронумерованная последовательность ячеек .
Размер 1 ячейки равен байту - число не более 256 , 2 байта - уже 65536 , и т.д.
4-байтная организация ячеек принята в качестве эталона .
x86 регистры имеют 4-байтную длину . Адреса памяти также 4-байтны и еще называются pointers.
Процессор по-разному может иметь доступ к данным .
1. immediate mode - например , когда нам нужно просто установить
регистр в 0
2. register addressing mode
3. direct addressing mode - содержимое памяти напрямую
копируется в регистр
4. indexed addressing mode - используется промежуточный адрес
5. indirect addressing mode - использование двойной промежуточный адрес
6. base pointer addressing mode - используется произвольное
адресное смещение , не кратное 4 .
Наша первая программа будет называться exit.s . Она ничего не будет делать :-)
.section .data
.section .text
.globl _start
_start:
movl $1, %eax
movl $0, %ebx
int $0x80
Здесь movl $1, %eax - номер системной команды ядра
Единица помещается в регистр eax . В большинстве команд 1-й операнд , как в данном случае ,
является источником , второй - назначением (destination) , например : addl, subl, imull.
Результат хранится в destination . Знак $1 говорит о том , что используется immediate mode-адресация.
Число 1 есть ни что иное , как номер kernel exit system call.
При этом для выполнения команды exit требуется параметр , который загружается в регистр ebx .
movl $0, %ebx - номер статуса , с которым мы возвращаемся в операционную систему .
int $0x80 - это прерывание . Оно прерывает команду и передает управление ядру .
Для компиляции нужно использовать следующий алгоритм :
as exit.s -o exit.o
ld exit.o -o exit
./exit
В следующей программе - maximum.s -мы найдем максимум из массива чисел , при этом :
%edi - будет храниться текущий индекс массива
%ebx - будет храниться максимум
%eax - будет храниться текущий элемент массива
.section .data
data_items:
.long 3,67,34,222,45,75,54,34,44,33,22,11,66,0
.section .text
.globl _start
_start:
movl $0, %edi
movl data_items(,%edi,4), %eax
movl %eax, %ebx
start_loop: # start loop
cmpl $0, %eax # check to see if we've hit the end
je loop_exit
incl %edi # load next value
movl data_items(,%edi,4), %eax
cmpl %ebx, %eax # compare values
jle start_loop
movl %eax, %ebx # move the value as the largest
jmp start_loop # jump to loop beginning
loop_exit:
movl $1, %eax #1 is the exit() syscall
int $0x80
Компиляция :
as maximum.s -o maximum.o
ld maximum.o -o maximum
Перед массивом мы поставили тип .long .
В нашем примере для массива будет зарезервировано 14*4=56 байт .
Кроме типа .long , имеются другие типы :
.byte
.int
.ascii - для хранения символьных строк
Ноль поставлен в конец списка специально для реализации поискового алгоритма .
Рассмотрим команду
movl data_items(,%edi,4), %eax
Смысл команды в следующем - идем в начало массива , поскольку edi=0 , берем 4 байта и пишем
их в eax , т.е. сохраняем первый элемент массива в eax - это число 3 .
Дальше идет цикл , где все достаточно понятно .
Режим адресации Addressing Modes .
Ссылка на адрес памяти представлена в виде :
ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER)
Вычисление идет с помощью следующего алгоритма :
FINAL ADDRESS = ADDRESS_OR_OFFSET + %BASE_OR_OFFSET + MULTIPLIER * %INDEX
Режимы адресации :
direct addressing mode
movl ADDRESS, %eax
indexed addressing mode
movl string_start(,%ecx,1), %eax
indirect addressing mode
movl (%eax), %ebx
base pointer addressing mode
movl 4(%eax), %ebx
immediate mode
movl $12, %eax
Рассмотрим использование функций . В нее могут входить :
function name
function parameters
local variables
static variables
global variables
return address
return value
Несколько слов о стеке . Визуально представим его как стопку бумаг на столе ,
с которой работаем сверху . Стек находится в верхних адресах памяти .
Положить значение в стек можно с помощью команды pushl , удалить - popl .
Стековый регистр %esp всегда указывает на текущий адрес стека .
Для доступа к произвольному значению стека нужно использовать %esp с indirect addressing mode.
movl (%esp), %eax
Со смещением :
movl 4(%esp), %eax
Перед вызовом функции параметры функции попадают в стек в порядке , обратном их обьявлению .
Стек после вызова команды call будет выглядеть так :
Parameter #N
...
Parameter 2
Parameter 1
Return Address --- (%esp)
Для того , чтобы использовать стек для нескольких функций , ссуществует регистр %ebp.
Перед вызовом функции нужно сохранить в нем свой собственный стековый указатель.
Если нам нужно например зарезервировать под локальные переменные функции дополнительно 8 байт ,
мы пишем :
subl $8, %esp
После этого стек выглядит так :
Parameter #N --- N*4+4(%ebp)
...
Parameter 2 <--- 12(%ebp)
Parameter 1 <--- 8(%ebp)
Return Address <--- 4(%ebp)
Old %ebp <--- (%ebp)
Local Variable 1 <--- -4(%ebp)
Local Variable 2 <--- -8(%ebp) and (%esp)
После выполнения функция делает следующее :
1. Возврашаемое значение сохраняется в %eax.
2. Стек восстанавливается на состояние до вызова
3. Команда ret возвращает управление :
movl %ebp, %esp
popl %ebp
ret
Пример вызова функции - power.s
.section .data
.section .text
.globl _start
_start:
pushl $3 #push second argument
pushl $2 #push first argument
call power #call the function
addl $8, %esp #move the stack pointer back
pushl %eax #save the first answer before
#calling the next function
pushl $2 #push second argument
pushl $5 #push first argument
call power #call the function
addl $8, %esp #move the stack pointer back
popl %ebx #The second answer is already
#in %eax. We saved the
#first answer onto the stack,
#so now we can just pop it
#out into %ebx
addl %eax, %ebx #add them together
#the result is in %ebx
movl $1, %eax #exit (%ebx is returned)
int $0x80
.type power, @function
power:
pushl %ebp #save old base pointer
movl %esp, %ebp #make stack pointer the base pointer
subl $4, %esp #get room for our local storage
movl 8(%ebp), %ebx #put first argument in %eax
movl 12(%ebp), %ecx #put second argument in %ecx
movl %ebx, -4(%ebp) #store current result
power_loop_start:
cmpl $1, %ecx #if the power is 1, we are done
je end_power
movl -4(%ebp), %eax #move the current result into %eax
imull %ebx, %eax #multiply the current result by
#the base number
movl %eax, -4(%ebp) #store the current result
decl %ecx #decrease the power
jmp power_loop_start #run for the next power
end_power:
movl -4(%ebp), %eax #return value goes in %eax
movl %ebp, %esp #restore the stack pointer
popl %ebp #restore the base pointer
ret
Следующий пример рекурсивного вызова функции вычисляет факториал числа .
.section .data
.section .text
.globl _start
.globl factorial #this is unneeded unless we want to share
#this function among other programs
_start:
pushl $4 #The factorial takes one argument - the
#number we want a factorial of. So, it
#gets pushed
call factorial #run the factorial function
addl $4, %esp #Scrubs the parameter that was pushed on
#the stack
movl %eax, %ebx #factorial returns the answer in %eax, but
#we want it in %ebx to send it as our exit
#status
movl $1, %eax #call the kernel's exit function
int $0x80
#This is the actual function definition
.type factorial,@function
factorial:
pushl %ebp #standard function stuff - we have to
#restore %ebp to its prior state before
#returning, so we have to push it
movl %esp, %ebp #This is because we don't want to modify
#the stack pointer, so we use %ebp.
movl 8(%ebp), %eax #This moves the first argument to %eax
#4(%ebp) holds the return address, and
#8(%ebp) holds the first parameter
cmpl $1, %eax #If the number is 1, that is our base
#case, and we simply return (1 is
#already in %eax as the return value)
je end_factorial
decl %eax #otherwise, decrease the value
pushl %eax #push it for our call to factorial
call factorial #call factorial
movl 8(%ebp), %ebx #%eax has the return value, so we
#reload our parameter into %ebx
imull %ebx, %eax #multiply that by the result of the
#last call to factorial (in %eax)
#the answer is stored in %eax, which
47
Chapter 4. All About Functions
#is good since that's where return
#values go.
end_factorial:
movl %ebp, %esp #standard function return stuff - we
popl %ebp #have to restore %ebp and %esp to where
#they were before the function started
ret #return to the function (this pops the
#return value, too)
Работа с файлами
Файл в линуксе - это потоковый (stream) массив байт .
Каждый файл имеет уникальный id-шник - файловый дескриптор .
При открытии файла нужно указать имя , режим (read,write,create).
Это делает системный вызов open . При этом в %eax будет сохранен номер 5 .
read - system call 3
write - system call 4
close - system call 6
Адрес 1-го байта файла будет сохранен в %ebx. Режим работы с файлом ,
представленный в форме числа , будет сохранен в %ecx (0-на чтение,03101-на запись).
Системные права на файл сохраняются в %edx (например,0666).
После чего - кернел вернет файловый дескриптор в eax .
В ecx будет текущая позиция в открытом файле , размер файла - в edx .
При чтении файла линукс сохраняет байты в части памяти , которая называется буфером .
Можно задавать размер считываемого блока .
Для задания статического буфера достаточно зарезервировать массив .long или .byte в начале программы.
В коде появляется новая секция .bss , которая просто резервирует блок памяти , не инициализируя его.
.section .bss
.lcomm my_buffer, 500
В дальнейшем этот буфер можно использовать так :
movl $my_buffer, %ecx
movl 500, %edx
movl 3, %eax
int $0x80
Любая линуксовая программа при запуске открывает
по крфйней мере 3 файловых дескриптора :
STDIN - read-only файл , представляющий клавиатуру , =0
STDOUT - write-only файл , монитор , =1
STDERR - write-only файл , =2
Взаимодействие между процессами также осуществляется через файлы , которые называются pipes.
Напишем программу toupper.s , которая читает содержимое одного файла , переводит содержимое из нижнего
регистра в верхний и переписывает в другой файл .
.section .data
#######CONSTANTS########
#system call numbers
.equ SYS_OPEN, 5
.equ SYS_WRITE, 4
.equ SYS_READ, 3
.equ SYS_CLOSE, 6
.equ SYS_EXIT, 1
#options for open (look at
#/usr/include/asm/fcntl.h for
#various values. You can combine them
#by adding them or ORing them)
#This is discussed at greater length
#in "Counting Like a Computer"
.equ O_RDONLY, 0
.equ O_CREAT_WRONLY_TRUNC, 03101
#standard file descriptors
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2
#system call interrupt
.equ LINUX_SYSCALL, 0x80
.equ END_OF_FILE, 0 #This is the return value
#of read which means we've
#hit the end of the file
.equ NUMBER_ARGUMENTS, 2
.section .bss
#Buffer - this is where the data is loaded into
# from the data file and written from
# into the output file. This should
# never exceed 16,000 for various
# reasons.
.equ BUFFER_SIZE, 500
.lcomm BUFFER_DATA, BUFFER_SIZE
.section .text
#STACK POSITIONS
.equ ST_SIZE_RESERVE, 8
.equ ST_FD_IN, -4
.equ ST_FD_OUT, -8
.equ ST_ARGC, 0 #Number of arguments
.equ ST_ARGV_0, 4 #Name of program
.equ ST_ARGV_1, 8 #Input file name
.equ ST_ARGV_2, 12 #Output file name
.globl _start
_start:
###INITIALIZE PROGRAM###
#save the stack pointer
movl %esp, %ebp
#Allocate space for our file descriptors
#on the stack
subl $ST_SIZE_RESERVE, %esp
open_files:
open_fd_in:
###OPEN INPUT FILE###
#open syscall
movl $SYS_OPEN, %eax
#input filename into %ebx
movl ST_ARGV_1(%ebp), %ebx
#read-only flag
movl $O_RDONLY, %ecx
#this doesn't really matter for reading
movl $0666, %edx
#call Linux
int $LINUX_SYSCALL
store_fd_in:
#save the given file descriptor
movl %eax, ST_FD_IN(%ebp)
open_fd_out:
###OPEN OUTPUT FILE###
#open the file
movl $SYS_OPEN, %eax
#output filename into %ebx
movl ST_ARGV_2(%ebp), %ebx
#flags for writing to the file
movl $O_CREAT_WRONLY_TRUNC, %ecx
#mode for new file (if it's created)
movl $0666, %edx
#call Linux
int $LINUX_SYSCALL
store_fd_out:
#store the file descriptor here
movl %eax, ST_FD_OUT(%ebp)
###BEGIN MAIN LOOP###
read_loop_begin:
###READ IN A BLOCK FROM THE INPUT FILE###
movl $SYS_READ, %eax
#get the input file descriptor
movl ST_FD_IN(%ebp), %ebx
#the location to read into
movl $BUFFER_DATA, %ecx
#the size of the buffer
movl $BUFFER_SIZE, %edx
#Size of buffer read is returned in %eax
int $LINUX_SYSCALL
###EXIT IF WE'VE REACHED THE END###
#check for end of file marker
cmpl $END_OF_FILE, %eax
#if found or on error, go to the end
jle end_loop
continue_read_loop:
###CONVERT THE BLOCK TO UPPER CASE###
pushl $BUFFER_DATA #location of buffer
pushl %eax #size of the buffer
call convert_to_upper
popl %eax #get the size back
addl $4, %esp #restore %esp
###WRITE THE BLOCK OUT TO THE OUTPUT FILE###
#size of the buffer
movl %eax, %edx
movl $SYS_WRITE, %eax
#file to use
movl ST_FD_OUT(%ebp), %ebx
#location of the buffer
movl $BUFFER_DATA, %ecx
int $LINUX_SYSCALL
###CONTINUE THE LOOP###
jmp read_loop_begin
end_loop:
###CLOSE THE FILES###
#NOTE - we don't need to do error checking
# on these, because error conditions
# don't signify anything special here
movl $SYS_CLOSE, %eax
movl ST_FD_OUT(%ebp), %ebx
int $LINUX_SYSCALL
movl $SYS_CLOSE, %eax
movl ST_FD_IN(%ebp), %ebx
int $LINUX_SYSCALL
###EXIT###
movl $SYS_EXIT, %eax
movl $0, %ebx
int $LINUX_SYSCALL
###CONSTANTS##
#The lower boundary of our search
.equ LOWERCASE_A, 'a'
#The upper boundary of our search
.equ LOWERCASE_Z, 'z'
#Conversion between upper and lower case
.equ UPPER_CONVERSION, 'A' - 'a'
###STACK STUFF###
.equ ST_BUFFER_LEN, 8 #Length of buffer
.equ ST_BUFFER, 12 #actual buffer
convert_to_upper:
pushl %ebp
movl %esp, %ebp
###SET UP VARIABLES###
movl ST_BUFFER(%ebp), %eax
movl ST_BUFFER_LEN(%ebp), %ebx
movl $0, %edi
#if a buffer with zero length was given
#to us, just leave
cmpl $0, %ebx
je end_convert_loop
convert_loop:
#get the current byte
movb (%eax,%edi,1), %cl
#go to the next byte unless it is between
#'a' and 'z'
cmpb $LOWERCASE_A, %cl
jl next_byte
cmpb $LOWERCASE_Z, %cl
jg next_byte
#otherwise convert the byte to uppercase
addb $UPPER_CONVERSION, %cl
#and store it back
movb %cl, (%eax,%edi,1)
next_byte:
incl %edi #next byte
cmpl %edi, %ebx #continue unless
#we've reached the
#end
jne convert_loop
end_convert_loop:
#no return value, just leave
movl %ebp, %esp
popl %ebp
ret
Компиляция программы :
as toupper.s -o toupper.o
ld toupper.o -o toupper
Запуск программы с параметрами :
./toupper toupper.s toupper.uppercase
Данные , сохраняемые на диске , могут быть структурированными .
Структура состоит из записей и полей фиксированной длины .
Представим структуру :
1. Имя - 40 байт
2. Фамилия - 40 байт
3. Адрес - 240 байт
4. Возраст - 4 байта
Запишем структуру в файле record-def.s:
.equ RECORD_FIRSTNAME, 0
.equ RECORD_LASTNAME, 40
.equ RECORD_ADDRESS, 80
.equ RECORD_AGE, 320
.equ RECORD_SIZE, 324
Запишем набор констант в другой файл linux.s:
#Common Linux Definitions
#System Call Numbers
.equ SYS_EXIT, 1
.equ SYS_READ, 3
.equ SYS_WRITE, 4
.equ SYS_OPEN, 5
.equ SYS_CLOSE, 6
.equ SYS_BRK, 45
#System Call Interrupt Number
.equ LINUX_SYSCALL, 0x80
#Standard File Descriptors
.equ STDIN, 0
.equ STDOUT, 1
.equ STDERR, 2
#Common Status Codes
.equ END_OF_FILE, 0
Напишем 3 программы , использующие данную структуру .
Первая будет писать данные в файл . Вторая будет выводить данные из файла на экран .
Третья будет добавлять 1 год в поле возраст для каждой записи.
Также нам будут нужны 2 функции для чтения-записи - вот они :
.include "record-def.s"
.include "linux.s"
#PURPOSE: This function reads a record from the file
# descriptor
#
#INPUT: The file descriptor and a buffer
#
#OUTPUT: This function writes the data to the buffer
# and returns a status code.
#
#STACK LOCAL VARIABLES
.equ ST_READ_BUFFER, 8
.equ ST_FILEDES, 12
.section .text
.globl read_record
.type read_record, @function
read_record:
pushl %ebp
movl %esp, %ebp
pushl %ebx
movl ST_FILEDES(%ebp), %ebx
movl ST_READ_BUFFER(%ebp), %ecx
movl $RECORD_SIZE, %edx
movl $SYS_READ, %eax
int $LINUX_SYSCALL
#NOTE - %eax has the return value, which we will
# give back to our calling program
popl %ebx
movl %ebp, %esp
popl %ebp
ret
.include "linux.s"
.include "record-def.s"
#PURPOSE: This function writes a record to
# the given file descriptor
#
#INPUT: The file descriptor and a buffer
#
#OUTPUT: This function produces a status code
#
#STACK LOCAL VARIABLES
.equ ST_WRITE_BUFFER, 8
.equ ST_FILEDES, 12
.section .text
.globl write_record
.type write_record, @function
write_record:
pushl %ebp
movl %esp, %ebp
pushl %ebx
movl $SYS_WRITE, %eax
movl ST_FILEDES(%ebp), %ebx
movl ST_WRITE_BUFFER(%ebp), %ecx
movl $RECORD_SIZE, %edx
int $LINUX_SYSCALL
#NOTE - %eax has the return value, which we will
# give back to our calling program
popl %ebx
movl %ebp, %esp
popl %ebp
ret
Теперь первая программа - она пишет записи в файл :
.include "linux.s"
.include "record-def.s"
.section .data
#Constant data of the records we want to write
#Each text data item is padded to the proper
#length with null (i.e. 0) bytes.
#.rept is used to pad each item. .rept tells
#the assembler to repeat the section between
#.rept and .endr the number of times specified.
#This is used in this program to add extra null
#characters at the end of each field to fill
#it up
record1:
.ascii "Fredrick\0"
.rept 31 #Padding to 40 bytes
.byte 0
.endr
.ascii "Bartlett\0"
.rept 31 #Padding to 40 bytes
.byte 0
.endr
.ascii "4242 S Prairie\nTulsa, OK 55555\0"
.rept 209 #Padding to 240 bytes
.byte 0
.endr
.long 45
record2:
.ascii "Marilyn\0"
.rept 32 #Padding to 40 bytes
.byte 0
.endr
.ascii "Taylor\0"
.rept 33 #Padding to 40 bytes
.byte 0
.endr
.ascii "2224 S Johannan St\nChicago, IL 12345\0"
.rept 203 #Padding to 240 bytes
.byte 0
.endr
.long 29
record3:
.ascii "Derrick\0"
.rept 32 #Padding to 40 bytes
.byte 0
.endr
.ascii "McIntire\0"
.rept 31 #Padding to 40 bytes
.byte 0
.endr
.ascii "500 W Oakland\nSan Diego, CA 54321\0"
.rept 206 #Padding to 240 bytes
.byte 0
.endr
.long 36
#This is the name of the file we will write to
file_name:
.ascii "test.dat\0"
.equ ST_FILE_DESCRIPTOR, -4
.globl _start
_start:
#Copy the stack pointer to %ebp
movl %esp, %ebp
#Allocate space to hold the file descriptor
subl $4, %esp
#Open the file
movl $SYS_OPEN, %eax
movl $file_name, %ebx
movl $0101, %ecx #This says to create if it
#doesn't exist, and open for
#writing
movl $0666, %edx
int $LINUX_SYSCALL
#Store the file descriptor away
movl %eax, ST_FILE_DESCRIPTOR(%ebp)
#Write the first record
pushl ST_FILE_DESCRIPTOR(%ebp)
pushl $record1
call write_record
addl $8, %esp
#Write the second record
pushl ST_FILE_DESCRIPTOR(%ebp)
pushl $record2
call write_record
addl $8, %esp
#Write the third record
pushl ST_FILE_DESCRIPTOR(%ebp)
pushl $record3
call write_record
addl $8, %esp
#Close the file descriptor
movl $SYS_CLOSE, %eax
movl ST_FILE_DESCRIPTOR(%ebp), %ebx
int $LINUX_SYSCALL
#Exit the program
movl $SYS_EXIT, %eax
movl $0, %ebx
int $LINUX_SYSCALL
Компиляция
as write-records.s -o write-record.o
as write-record.s -o write-record.o
ld write-record.o write-records.o -o write-records
Теперь программа для чтения записей
Сначала напишем функцию count-chars.s:
.type count_chars, @function
.globl count_chars
#This is where our one parameter is on the stack
.equ ST_STRING_START_ADDRESS, 8
count_chars:
pushl %ebp
movl %esp, %ebp
#Counter starts at zero
movl $0, %ecx
#Starting address of data
movl ST_STRING_START_ADDRESS(%ebp), %edx
count_loop_begin:
#Grab the current character
movb (%edx), %al
#Is it null?
cmpb $0, %al
#If yes, we're done
je count_loop_end
#Otherwise, increment the counter and the pointer
incl %ecx
incl %edx
#Go back to the beginning of the loop
jmp count_loop_begin
count_loop_end:
#We're done. Move the count into %eax
#and return.
movl %ecx, %eax
popl %ebp
ret
Напишем еще одну функцию в файл write-newline.s:
.include "linux.s"
.globl write_newline
.type write_newline, @function
.section .data
newline:
.ascii "\n"
.section .text
.equ ST_FILEDES, 8
write_newline:
pushl %ebp
movl %esp, %ebp
movl $SYS_WRITE, %eax
movl ST_FILEDES(%ebp), %ebx
movl $newline, %ecx
movl $1, %edx
int $LINUX_SYSCALL
movl %ebp, %esp
popl %ebp
ret
Теперь сама программ чтения read-records.s:
.include "linux.s"
.include "record-def.s"
.section .data
file_name:
.ascii "test.dat\0"
.section .bss
.lcomm record_buffer, RECORD_SIZE
.section .text
#Main program
.globl _start
_start:
#These are the locations on the stack where
#we will store the input and output descriptors
#(FYI - we could have used memory addresses in
#a .data section instead)
.equ ST_INPUT_DESCRIPTOR, -4
.equ ST_OUTPUT_DESCRIPTOR, -8
#Copy the stack pointer to %ebp
movl %esp, %ebp
#Allocate space to hold the file descriptors
subl $8, %esp
#Open the file
movl $SYS_OPEN, %eax
movl $file_name, %ebx
movl $0, %ecx #This says to open read-only
movl $0666, %edx
int $LINUX_SYSCALL
#Save file descriptor
movl %eax, ST_INPUT_DESCRIPTOR(%ebp)
#Even though it's a constant, we are
#saving the output file descriptor in
#a local variable so that if we later
#decide that it isn't always going to
#be STDOUT, we can change it easily.
movl $STDOUT, ST_OUTPUT_DESCRIPTOR(%ebp)
record_read_loop:
pushl ST_INPUT_DESCRIPTOR(%ebp)
pushl $record_buffer
call read_record
addl $8, %esp
#Returns the number of bytes read.
#If it isn't the same number we
#requested, then it's either an
#end-of-file, or an error, so we're
#quitting
cmpl $RECORD_SIZE, %eax
jne finished_reading
#Otherwise, print out the first name
#but first, we must know it's size
pushl $RECORD_FIRSTNAME + record_buffer
call count_chars
addl $4, %esp
movl %eax, %edx
movl ST_OUTPUT_DESCRIPTOR(%ebp), %ebx
movl $SYS_WRITE, %eax
movl $RECORD_FIRSTNAME + record_buffer, %ecx
int $LINUX_SYSCALL
pushl ST_OUTPUT_DESCRIPTOR(%ebp)
call write_newline
addl $4, %esp
jmp record_read_loop
finished_reading:
movl $SYS_EXIT, %eax
movl $0, %ebx
int $LINUX_SYSCALL
Компиляция программы :
as read-record.s -o read-record.o
as count-chars.s -o count-chars.o
as write-newline.s -o write-newline.o
as read-records.s -o read-records.o
ld read-record.o count-chars.o write-newline.o read-records.o -o read-records
Теперь 3-я программа для модификации записей add-year.s:
.include "linux.s"
.include "record-def.s"
.section .data
input_file_name:
.ascii "test.dat\0"
output_file_name:
.ascii "testout.dat\0"
.section .bss
.lcomm record_buffer, RECORD_SIZE
#Stack offsets of local variables
.equ ST_INPUT_DESCRIPTOR, -4
.equ ST_OUTPUT_DESCRIPTOR, -8
.section .text
.globl _start
_start:
#Copy stack pointer and make room for local variables
movl %esp, %ebp
subl $8, %esp
#Open file for reading
movl $SYS_OPEN, %eax
movl $input_file_name, %ebx
movl $0, %ecx
movl $0666, %edx
int $LINUX_SYSCALL
movl %eax, ST_INPUT_DESCRIPTOR(%ebp)
#Open file for writing
movl $SYS_OPEN, %eax
movl $output_file_name, %ebx
movl $0101, %ecx
movl $0666, %edx
int $LINUX_SYSCALL
movl %eax, ST_OUTPUT_DESCRIPTOR(%ebp)
loop_begin:
pushl ST_INPUT_DESCRIPTOR(%ebp)
pushl $record_buffer
call read_record
addl $8, %esp
#Returns the number of bytes read.
#If it isn't the same number we
#requested, then it's either an
#end-of-file, or an error, so we're
#quitting
cmpl $RECORD_SIZE, %eax
jne loop_end
#Increment the age
incl record_buffer + RECORD_AGE
#Write the record out
pushl ST_OUTPUT_DESCRIPTOR(%ebp)
pushl $record_buffer
call write_record
addl $8, %esp
jmp loop_begin
loop_end:
movl $SYS_EXIT, %eax
movl $0, %ebx
int $LINUX_SYSCALL
Компиляция :
as add-year.s -o add-year.o
ld add-year.o read-record.o write-record.o -o add-year
Данные находятся в файле testout.dat.
Теперь напишем программу , которая контролирует ошибки и исключительные ситуации .
Добавим в предыдущую функцию add-year.s код , который в случае ошибки будет печатать
сообщение и прерывать программу .
Файл error-exit.s :
.include "linux.s"
.equ ST_ERROR_CODE, 8
.equ ST_ERROR_MSG, 12
.globl error_exit
.type error_exit, @function
error_exit:
pushl %ebp
movl %esp, %ebp
#Write out error code
movl ST_ERROR_CODE(%ebp), %ecx
pushl %ecx
call count_chars
popl %ecx
movl %eax, %edx
movl $STDERR, %ebx
movl $SYS_WRITE, %eax
int $LINUX_SYSCALL
#Write out error message
movl ST_ERROR_MSG(%ebp), %ecx
pushl %ecx
call count_chars
popl %ecx
movl %eax, %edx
movl $STDERR, %ebx
movl $SYS_WRITE, %eax
int $LINUX_SYSCALL
pushl $STDERR
call write_newline
#Exit with status 1
movl $SYS_EXIT, %eax
movl $1, %ebx
int $LINUX_SYSCALL
#Open file for reading
movl $SYS_OPEN, %eax
movl $input_file_name, %ebx
movl $0, %ecx
movl $0666, %edx
int $LINUX_SYSCALL
movl %eax, INPUT_DESCRIPTOR(%ebp)
#This will test and see if %eax is
#negative. If it is not negative, it
#will jump to continue_processing.
#Otherwise it will handle the error
#condition that the negative number
#represents.
cmpl $0, %eax
jl continue_processing
#Send the error
.section .data
no_open_file_code:
.ascii "0001: \0"
no_open_file_msg:
.ascii "Can't Open Input File\0"
.section .text
pushl $no_open_file_msg
pushl $no_open_file_code
call error_exit
continue_processing:
#Rest of program
Компиляция :
as add-year.s -o add-year.o
as error-exit.s -o error-exit.o
ld add-year.o write-newline.o error-exit.o read-record.o write-record.o countchars.o -o add-year
Теперь , если запустить програму без файла данных , выход будет корректным .
<>
Теперь рассмотрим вопрос использования библиотек .
Напишем простую программу helloworld-nolib.s
#PURPOSE: This program writes the message "hello world" and
# exits
#
.include "linux.s"
.section .data
helloworld:
.ascii "hello world\n"
helloworld_end:
.equ helloworld_len, helloworld_end - helloworld
.section .text
.globl _start
_start:
movl $STDOUT, %ebx
movl $helloworld, %ecx
movl $helloworld_len, %edx
movl $SYS_WRITE, %eax
int $LINUX_SYSCALL
movl $0, %ebx
movl $SYS_EXIT, %eax
int $LINUX_SYSCALL
Теперь вариант программы с использованием библиотеки - helloworld-lib :
#PURPOSE: This program writes the message "hello world" and
# exits
.section .data
helloworld:
.ascii "hello world\n\0"
.section .text
.globl _start
_start:
pushl $helloworld
call printf
pushl $0
call exit
Компиляция программы без библиотеки :
as helloworld-nolib.s -o helloworld-nolib.o
ld helloworld-nolib.o -o helloworld-nolib
Компиляция программы с библиотекой :
as helloworld-lib.s -o helloworld-lib.o
ld -dynamic-linker /lib/ld-linux.so.2 -o helloworld-lib helloworld-lib.o -lc
Опция -dynamic-linker прилинковывает внешнюю библиотеку /lib/ld-linux.so.2.
Опция -lc прилинковывает си-шную библиотеку (libc.so) со стандартными функциями .
При запуске второй программы сначала грузится /lib/ld-linux.so.2 - это динамический линкер.
Он ищет файл libc.so , ищет в нем стандартные функции типа printf , после чего загружает библиотеку.
Если выпполнить команду
ldd ./helloworld-lib
будет выведено что-то типа :
libc.so.6 => /lib/libc.so.6 (0x4001d000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x400000000)
Как самому создать библиотеку ?
Можно использовать код из предыдущего раздела :
Выполним команду :
as write-record.s -o write-record.o
as read-record.s -o read-record.o
ld -shared write-record.o read-record.o -o librecord.so
2 файла слинкованы в библиотеку librecord.so.
Прилинковать эту библиотеку можно так :
as write-records.s -o write-records
ld -L.-dynamic-linker /lib/ld-linux.so.2 -o write-records -lrecord write-records.o
Команда -L прилинковывает библиотеку librecord.so ,
которая должна лежать в текущей директории.
Необходимо перед выполнением прописать путь
LD_LIBRARY_PATH=.
export LD_LIBRARY_PATH
или так
setenv LD_LIBRARY_PATH .
Память - это последовательность пронумерованных ячеек.
В памяти хранятся как исполняемый код , так и данные .
Ячейка - это один байт , в ней может храниться число < 255.
Word - это 4 байта .
Адрес - номер ячейки , т.е. 0,1,2...
Pointer - регистр или память , в которой хранится адрес .
В ассемблерных программах данные и код разнесены по разным секциям - .section .
Секция .text загружается по адресу 0x08048000 , затем сразу грузится секция .data .
Стек грузится по самому старшему адресу 0xbfffffff и будет расти вниз .
В самый верхний байт стека пишется ноль , затем внешние параметры программы ,
затем имя программы , затем переменные программы .
При старте программы адрес стека -pointer- , с которого начинаются переменные программы ,
будет располагаться в %esp.
Командой pushl %eax этот адрес заносится в память для работы .
Сам код программы располагается в нижней памяти и сверху ограничен адресом ,
который называется break.
Различие между понятием физическая память и виртуальная в следующем :
когда ядро находит в памяти кусок , достаточный для того , чтобы загрузить программу ,
оно дает процессору интерпретировать этот адрес как стартовый адрес 0x0804800
для загрузки . То же самое и для стека . Т.е. прогамма начинает использовать виртуальный
псевдо-адрес , не соответствующий физическому . Процесс переназначения физического адреса
виртуальному называется mapping - маппинг .
Виртуальная память может быть привязана не только к физической , но и к дисковой ,
для этого служит swap-раздел на диске .
Память разделена на страницы . Страница - это 4096 байт , или 4 килобайта .
Память можно добавлять динамически во время работы , и кернел будет отдвигать т.н. адрес
break туда , куда мы скажем . На асм-е отодвигание адреса break выполняется системным вызовом brk -
системный номер 45 . Адрес break хранится в eax .
Пользователь не может следить за корректным отодвиганием break , этим занимается memory manager.
Они имеют 2 функции - allocate и deallocate , которые просто нужно расставлять в правильном порядке
в своем коде .
Теперь напишем свой собственный memory manager .
.section .data
#######GLOBAL VARIABLES########
#This points to the beginning of the memory we are managing
heap_begin:
.long 0
#This points to one location past the memory we are managing
current_break:
.long 0
######STRUCTURE INFORMATION####
#size of space for memory region header
.equ HEADER_SIZE, 8
#Location of the "available" flag in the header
.equ HDR_AVAIL_OFFSET, 0
#Location of the size field in the header
.equ HDR_SIZE_OFFSET, 4
###########CONSTANTS###########
.equ UNAVAILABLE, 0 #This is the number we will use to mark
#space that has been given out
.equ AVAILABLE, 1 #This is the number we will use to mark
#space that has been returned, and is
#available for giving
.equ SYS_BRK, 45 #system call number for the break
#system call
.equ LINUX_SYSCALL, 0x80 #make system calls easier to read
.section .text
##########FUNCTIONS############
##allocate_init##
#PURPOSE: call this function to initialize the
# functions (specifically, this sets heap_begin and
# current_break). This has no parameters and no
.globl allocate_init
.type allocate_init,@function
allocate_init:
pushl %ebp #standard function stuff
movl %esp, %ebp
#If the brk system call is called with 0 in %ebx, it
#returns the last valid usable address
movl $SYS_BRK, %eax #find out where the break is
movl $0, %ebx
int $LINUX_SYSCALL
incl %eax #%eax now has the last valid
#address, and we want the
#memory location after that
movl %eax, current_break #store the current break
movl %eax, heap_begin #store the current break as our
#first address. This will cause
#the allocate function to get
#more memory from Linux the
#first time it is run
movl %ebp, %esp #exit the function
popl %ebp
ret
#####END OF FUNCTION#######
##allocate##
#PURPOSE: This function is used to grab a section of
# memory. It checks to see if there are any
# free blocks, and, if not, it asks Linux
# for a new one.
#
#PARAMETERS: This function has one parameter - the size
# of the memory block we want to allocate
#RETURN VALUE:
# This function returns the address of the
# allocated memory in %eax. If there is no
# memory available, it will return 0 in %eax
######PROCESSING########
#Variables used:
#
# %ecx - hold the size of the requested memory
# (first/only parameter)
# %eax - current memory region being examined
# %ebx - current break position
# %edx - size of current memory region
#
#We scan through each memory region starting with
#heap_begin. We look at the size of each one, and if
#it has been allocated. If it's big enough for the
#requested size, and its available, it grabs that one.
#If it does not find a region large enough, it asks
#Linux for more memory. In that case, it moves
#current_break up
.globl allocate
.type allocate,@function
.equ ST_MEM_SIZE, 8 #stack position of the memory size
#to allocate
allocate:
pushl %ebp #standard function stuff
movl %esp, %ebp
movl ST_MEM_SIZE(%ebp), %ecx #%ecx will hold the size
#we are looking for (which is the first
#and only parameter)
movl heap_begin, %eax #%eax will hold the current
#search location
movl current_break, %ebx #%ebx will hold the current
#break
alloc_loop_begin: #here we iterate through each
#memory region
cmpl %ebx, %eax #need more memory if these are equal
je move_break
#grab the size of this memory
movl HDR_SIZE_OFFSET(%eax), %edx
#If the space is unavailable, go to the
cmpl $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax)
je next_location #next one
cmpl %edx, %ecx #If the space is available, compare
jle allocate_here #the size to the needed size. If its
#big enough, go to allocate_here
next_location:
addl $HEADER_SIZE, %eax #The total size of the memory
addl %edx, %eax #region is the sum of the size
#requested (currently stored
#in %edx), plus another 8 bytes
#for the header (4 for the
#AVAILABLE/UNAVAILABLE flag,
#and 4 for the size of the
#region). So, adding %edx and $8
#to %eax will get the address
#of the next memory region
jmp alloc_loop_begin #go look at the next location
allocate_here: #if we've made it here,
#that means that the
#region header of the region
#to allocate is in %eax
#mark space as unavailable
movl $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax)
addl $HEADER_SIZE, %eax #move %eax past the header to
#the usable memory (since
#that's what we return)
movl %ebp, %esp #return from the function
popl %ebp
ret
move_break: #if we've made it here, that
#means that we have exhausted
#all addressable memory, and
#we need to ask for more.
#%ebx holds the current
#endpoint of the data,
addl $HEADER_SIZE, %ebx #add space for the headers
#structure
addl %ecx, %ebx #add space to the break for
#the data requested
#now its time to ask Linux
#for more memory
pushl %eax #save needed registers
pushl %ecx
pushl %ebx
movl $SYS_BRK, %eax #reset the break (%ebx has
#the requested break point)
int $LINUX_SYSCALL
#under normal conditions, this should
#return the new break in %eax, which
#will be either 0 if it fails, or
#it will be equal to or larger than
#we asked for. We don't care
#in this program where it actually
#sets the break, so as long as %eax
#isn't 0, we don't care what it is
cmpl $0, %eax #check for error conditions
je error
popl %ebx #restore saved registers
popl %ecx
popl %eax
#set this memory as unavailable, since we're about to
#give it away
movl $UNAVAILABLE, HDR_AVAIL_OFFSET(%eax)
#set the size of the memory
movl %ecx, HDR_SIZE_OFFSET(%eax)
#move %eax to the actual start of usable memory.
#%eax now holds the return value
addl $HEADER_SIZE, %eax
movl %ebx, current_break #save the new break
movl %ebp, %esp #return the function
popl %ebp
ret
error:
movl $0, %eax #on error, we return zero
movl %ebp, %esp
popl %ebp
ret
########END OF FUNCTION########
##deallocate##
#PURPOSE:
# The purpose of this function is to give back
# a region of memory to the pool after we're done
# using it.
#
#PARAMETERS:
# The only parameter is the address of the memory
# we want to return to the memory pool.
#
#RETURN VALUE:
# There is no return value
#
#PROCESSING:
# If you remember, we actually hand the program the
# start of the memory that they can use, which is
# 8 storage locations after the actual start of the
# memory region. All we have to do is go back
# 8 locations and mark that memory as available,
# so that the allocate function knows it can use it.
.globl deallocate
.type deallocate,@function
#stack position of the memory region to free
.equ ST_MEMORY_SEG, 4
deallocate:
#since the function is so simple, we
#don't need any of the fancy function stuff
#get the address of the memory to free
#(normally this is 8(%ebp), but since
#we didn't push %ebp or move %esp to
#%ebp, we can just do 4(%esp)
movl ST_MEMORY_SEG(%esp), %eax
#get the pointer to the real beginning of the memory
subl $HEADER_SIZE, %eax
#mark it as available
movl $AVAILABLE, HDR_AVAIL_OFFSET(%eax)
#return
ret
########END OF FUNCTION##########
Компиляция :
as alloc.s -o alloc.o
В коде нет секции .start - эта программа используется другими программами как утилита .
В программе используется упрощенный механизм выделения памяти.
Функция allocate - меденная , deallocate - быстрая .
Вызов функции brk - системный и потому не так быстр , потому что при вызове brk и выделении памяти
происходит переключение процессора в режим kernel mode и затем обратно в user mode .
Это переключение еще называется context switch.
Chapter 10
В двоичной системе счисления разряд называется битом . В одном байте 8 бит .
В двоичной арифметике можно выполнять следующие операции :
AND
OR
NOT
XOR
1 AND 1 = 1
0 AND 1 = 0
1 AND 0 = 0
0 AND 0 = 1
1 OR 1 = 1
1 OR 0 = 1
0 OR 1 = 1
0 OR 0 = 0
NOT 1 = 0
NOT 0 = 1
XOR - то же , что и OR , только результат наоборот .
Например , XOR любого числа с самим собой всегда дает ноль , это очень быстрая операция ,
например для обнуления регистра :
xorl %eax, %eax
Имеются сдвиговые битовые операции - shrl :
Shift left 10010111 = 00101110
Rotate left 10010111 = 00101111
Они отличаются только крайним правым битом .
В процессоре имеется регистр program status register .
В нем откладываются результаты двоичных вычислений . В нем имеются различные флаги .
Например , флаг carry отражает результат выполнения инструкции cmpl
Инструкции условного перехода jge, jne, etc используют этот регистр .
Десятичные числа хранятся в форме :
экспонента + мантисса
например :
число 12345.2 хранится в форме : 1.23452 * 10^4.
1.23452 - это мантисса , 4 - экспонента .
Для получения отрицательного числа нужно сделать операцию NOT с положительным эквивалентом
и прибавить 1 .
Процессор x86 относится к типу little-endian systems . Это означает буквально следующее :
пусть в регистре находится 4-байтное число , и байты в регистре будут располагаться
в следующем порядке :
1-й байт - 45
2-й байт - 123
3-й байт - 76
4-й байт - 98
При перемещении числа в память порядок байтов в памяти будет перевернут :
1-й байт - 98
2-й байт - 76
3-й байт - 123
4-й байт - 45
Для различных архитектур это может дать различные результаты при чтении-записи файлов или сокетов.
Appendix
Следующий код показывает , как на асм-е можно написать gnome-приложение .
Программа показывает окно с единственной кнопкой .
gnome-example.s:
.section .data
###GNOME definitions - These were found in the GNOME
# header files for the C language
# and converted into their assembly
# equivalents
#GNOME Button Names
GNOME_STOCK_BUTTON_YES:
.ascii "Button_Yes\0"
GNOME_STOCK_BUTTON_NO:
.ascii "Button_No\0"
#Gnome MessageBox Types
GNOME_MESSAGE_BOX_QUESTION:
.ascii "question\0"
#Standard definition of NULL
.equ NULL, 0
#GNOME signal definitions
signal_destroy:
.ascii "destroy\0"
signal_delete_event:
.ascii "delete_event\0"
signal_clicked:
.ascii "clicked\0"
###Application-specific definitions
#Application information
app_id:
.ascii "gnome-example\0"
app_version:
.ascii "1.000\0"
app_title:
.ascii "Gnome Example Program\0"
#Text for Buttons and windows
button_quit_text:
.ascii "I Want to Quit the GNOME Example Program\0"
quit_question:
.ascii "Are you sure you want to quit?\0"
.section .bss
#Variables to save the created widgets in
.equ WORD_SIZE, 4
.lcomm appPtr, WORD_SIZE
.lcomm btnQuit, WORD_SIZE
.section .text
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
#Initialize GNOME libraries
pushl 12(%ebp) #argv
pushl 8(%ebp) #argc
pushl $app_version
pushl $app_id
call gnome_init
addl $16, %esp #recover the stack
#Create new application window
pushl $app_title #Window title
pushl $app_id #Application ID
call gnome_app_new
addl $8, %esp #recover the stack
movl %eax, appPtr #save the window pointer
#Create new button
pushl $button_quit_text #button text
call gtk_button_new_with_label
addl $4, %esp #recover the stack
movl %eax, btnQuit #save the button pointer
#Make the button show up inside the application window
pushl btnQuit
pushl appPtr
call gnome_app_set_contents
addl $8, %esp
#Makes the button show up (only after it's window
#shows up, though)
pushl btnQuit
call gtk_widget_show
addl $4, %esp
#Makes the application window show up
pushl appPtr
call gtk_widget_show
addl $4, %esp
#Have GNOME call our delete_handler function
#whenever a "delete" event occurs
pushl $NULL #extra data to pass to our
#function (we don't use any)
pushl $delete_handler #function address to call
pushl $signal_delete_event #name of the signal
pushl appPtr #widget to listen for events on
call gtk_signal_connect
addl $16, %esp #recover stack
#Have GNOME call our destroy_handler function
#whenever a "destroy" event occurs
pushl $NULL #extra data to pass to our
#function (we don't use any)
pushl $destroy_handler #function address to call
pushl $signal_destroy #name of the signal
pushl appPtr #widget to listen for events on
call gtk_signal_connect
addl $16, %esp #recover stack
#Have GNOME call our click_handler function
#whenever a "click" event occurs. Note that
#the previous signals were listening on the
#application window, while this one is only
#listening on the button
pushl $NULL
pushl $click_handler
pushl $signal_clicked
pushl btnQuit
call gtk_signal_connect
addl $16, %esp
#Transfer control to GNOME. Everything that
#happens from here out is in reaction to user
#events, which call signal handlers. This main
#function just sets up the main window and connects
#signal handlers, and the signal handlers take
#care of the rest
call gtk_main
#After the program is finished, leave
movl $0, %eax
leave
ret
#A "destroy" event happens when the widget is being
#removed. In this case, when the application window
#is being removed, we simply want the event loop to
#quit
destroy_handler:
pushl %ebp
movl %esp, %ebp
#This causes gtk to exit it's event loop
#as soon as it can.
call gtk_main_quit
movl $0, %eax
leave
ret
#A "delete" event happens when the application window
#gets clicked in the "x" that you normally use to
#close a window
delete_handler:
movl $1, %eax
ret
#A "click" event happens when the widget gets clicked
click_handler:
pushl %ebp
movl %esp, %ebp
#Create the "Are you sure" dialog
pushl $NULL #End of buttons
pushl $GNOME_STOCK_BUTTON_NO #Button 1
pushl $GNOME_STOCK_BUTTON_YES #Button 0
pushl $GNOME_MESSAGE_BOX_QUESTION #Dialog type
pushl $quit_question #Dialog mesasge
call gnome_message_box_new
addl $16, %esp #recover stack
#%eax now holds the pointer to the dialog window
#Setting Modal to 1 prevents any other user
#interaction while the dialog is being shown
pushl $1
pushl %eax
call gtk_window_set_modal
popl %eax
addl $4, %esp
#Now we show the dialog
pushl %eax
call gtk_widget_show
popl %eax
#This sets up all the necessary signal handlers
#in order to just show the dialog, close it when
#one of the buttons is clicked, and return the
#number of the button that the user clicked on.
#The button number is based on the order the buttons
#were pushed on in the gnome_message_box_new function
pushl %eax
call gnome_dialog_run_and_close
addl $4, %esp
#Button 0 is the Yes button. If this is the
#button they clicked on, tell GNOME to quit
#it's event loop. Otherwise, do nothing
cmpl $0, %eax
jne click_handler_end
call gtk_main_quit
click_handler_end:
leave
ret
Компиляция :
as gnome-example.s -o gnome-example.o
gcc gnome-example.o 'gnome-config --libs gnomeui' -o gnome-example
В программе создаются видгеты , которые вызываются из gtk_main.
Теперь то же самое на Си - gnome-example-c.c:
#include
/* Program definitions */
#define MY_APP_TITLE "Gnome Example Program"
#define MY_APP_ID "gnome-example"
#define MY_APP_VERSION "1.000"
#define MY_BUTTON_TEXT "I Want to Quit the Example Program"
#define MY_QUIT_QUESTION "Are you sure you want to quit?"
/* Must declare functions before they are used */
int destroy_handler(gpointer window,
GdkEventAny *e,
gpointer data);
int delete_handler(gpointer window,
GdkEventAny *e,
gpointer data);
int click_handler(gpointer window,
GdkEventAny *e,
gpointer data);
int main(int argc, char **argv)
{
gpointer appPtr; /* application window */
gpointer btnQuit; /* quit button */
/* Initialize GNOME libraries */
gnome_init(MY_APP_ID, MY_APP_VERSION, argc, argv);
/* Create new application window */
appPtr = gnome_app_new(MY_APP_ID, MY_APP_TITLE);
/* Create new button */
btnQuit = gtk_button_new_with_label(MY_BUTTON_TEXT);
/* Make the button show up inside the application window */
gnome_app_set_contents(appPtr, btnQuit);
/* Makes the button show up */
gtk_widget_show(btnQuit);
/* Makes the application window show up */
gtk_widget_show(appPtr);
/* Connect the signal handlers */
gtk_signal_connect(appPtr, "delete_event",
GTK_SIGNAL_FUNC(delete_handler), NULL);
gtk_signal_connect(appPtr, "destroy",
GTK_SIGNAL_FUNC(destroy_handler), NULL);
gtk_signal_connect(btnQuit, "clicked",
GTK_SIGNAL_FUNC(click_handler), NULL);
/* Transfer control to GNOME */
gtk_main();
return 0;
}
/* Function to receive the "destroy" signal */
int destroy_handler(gpointer window,
GdkEventAny *e,
gpointer data)
{
/* Leave GNOME event loop */
gtk_main_quit();
return 0;
}
/* Function to receive the "delete_event" signal */
int delete_handler(gpointer window,
GdkEventAny *e,
gpointer data)
{
return 0;
}
/* Function to receive the "clicked" signal */
int click_handler(gpointer window,
GdkEventAny *e,
gpointer data)
{
gpointer msgbox;
int buttonClicked;
/* Create the "Are you sure" dialog */
msgbox = gnome_message_box_new(
MY_QUIT_QUESTION,
GNOME_MESSAGE_BOX_QUESTION,
GNOME_STOCK_BUTTON_YES,
GNOME_STOCK_BUTTON_NO,
NULL);
gtk_window_set_modal(msgbox, 1);
gtk_widget_show(msgbox);
/* Run dialog box */
buttonClicked = gnome_dialog_run_and_close(msgbox);
/* Button 0 is the Yes button. If this is the
button they clicked on, tell GNOME to quit
it's event loop. Otherwise, do nothing */
if(buttonClicked == 0)
{
gtk_main_quit();
}
return 0;
}
Компиляция :
gcc gnome-example-c.c 'gnome-config --cflags --libs gnomeui' -o gnome-example-c
Теперь версия на питоне :
#Import GNOME libraries
import gtk
import gnome.ui
####DEFINE CALLBACK FUNCTIONS FIRST####
#In Python, functions have to be defined before
#they are used, so we have to define our callback
#functions first.
def destroy_handler(event):
gtk.mainquit()
return 0
def delete_handler(window, event):
return 0
def click_handler(event):
#Create the "Are you sure" dialog
msgbox = gnome.ui.GnomeMessageBox(
"Are you sure you want to quit?",
gnome.ui.MESSAGE_BOX_QUESTION,
gnome.ui.STOCK_BUTTON_YES,
gnome.ui.STOCK_BUTTON_NO)
msgbox.set_modal(1)
msgbox.show()
result = msgbox.run_and_close()
#Button 0 is the Yes button. If this is the
#button they clicked on, tell GNOME to quit
#it's event loop. Otherwise, do nothing
if (result == 0):
gtk.mainquit()
return 0
#Create new application window
myapp = gnome.ui.GnomeApp(
"gnome-example", "Gnome Example Program")
#Create new button
mybutton = gtk.GtkButton(
"I Want to Quit the GNOME Example program")
myapp.set_contents(mybutton)
#Makes the button show up
mybutton.show()
#Makes the application window show up
myapp.show()
#Connect signal handlers
myapp.connect("delete_event", delete_handler)
myapp.connect("destroy", destroy_handler)
mybutton.connect("clicked", click_handler)
#Transfer control to GNOME
gtk.mainloop()
Запуск скрипта :
python gnome-example.py.
Список команд ассемблера
Перемещение данных:
1. movl
копирует word
movl %eax, %ebx копирует содержимое %eax в %ebx
2. movb
копирует byte
3. leal
загружает в eax не содержимое памяти , а адрес
например, leal 5(%ebp,%ecx,1),%eax загружает
адрес ( 5 + %ebp + 1*%ecx) и сохраняет его в in %eax
4. popl
выгружает из стека
5. pushl
загружает в стек
6. xchgl
обменивает 2 операнда
Операция с целыми числами:
1. adcl
сложение с переносом
2. addl
сложение
3.cdq
конвертация 1 байта в 2 байта
4. cmpl
сравнение
5. decl
вычитание
6. divl
деление
7. idivl
деление со знаком
8. imull
умножение
9. incl
инкремент
10. mull
умножение
11. negl
умножение на -1
12. sbbl
вычитание
13. subl
вычитание
Таблица наиболее system calls
1 exit
3 read
4 write
5 open
6 close
12 chdir
19 lseek
20 getpid
39 mkdir
40 rmdir
41 dup
42 pipe
45 brk
54 ioctl
Некоторые конструкции ассемблера .
Рассмотрим си-конструкцию :
if(a == b)
{
/* True Branch Code Here */
}
else
{
/* False Branch Code Here */
}
Эквивалент:
#Move a and b into registers for comparison
movl a, %eax
movl b, %ebx
#Compare
cmpl %eax, %ebx
#If True, go to true branch
je true_branch
false_branch: #This label is unnecessary,
#only here for documentation
#False Branch Code Here
#Jump to recovergence point
jmp reconverge
true_branch:
#True Branch Code Here
reconverge:
#Both branches recoverge to this point
При вызове функции с параметрами их нужно заносить
в стек в обратном порядке.
Си-код
printf("The number is %d", 88);
Асм:
.section .data
text_string:
.ascii "The number is %d\0"
.section .text
pushl $88
pushl $text_string
call printf
popl %eax
popl %eax
Глобальные и статические переменные нужно определять
в секциях .data или .bss
Си-код:
int my_global_var;
int foo()
{
int my_local_var;
my_local_var = 1;
my_global_var = 2;
return 0;
}
Асм:
.section .data
.lcomm my_global_var, 4
.type foo, @function
foo:
pushl %ebp #Save old base pointer
movl %esp, $ebp #make stack pointer base pointer
subl $4, %esp #Make room for my_local_var
.equ my_local_var, -4 #Can now use my_local_var to
#find the local variable
movl $1, my_local_var(%ebp)
movl $2, my_global_var
movl %ebp, %esp #Clean up function and return
popl %ebp
ret
Циклы
Си:
while(a < b)
{
/* Do stuff here */
}
Асм:
loop_begin:
movl a, %eax
movl b, %ebx
cmpl %eax, %ebx
jge loop_end
loop_body:
#Do stuff here
jmp loop_begin
loop_end:
#Finished looping
Си:
for(i=0; i < 100; i++)
{
/* Do process here */
}
Асм:
loop_initialize:
movl $100, %ecx
loop_begin:
#
#Do Process Here
#
#Decrement %ecx and loops if not zero
loop loop_begin
rest_of_program:
#Continues on to here
Структуры :
Си:
struct person {
char firstname[40];
char lastname[40];
int age;
};
Асм:
.equ PERSON_SIZE, 84
.equ PERSON_FIRSTNAME_OFFSET, 0
.equ PERSON_LASTNAME_OFFSET, 40
.equ PERSON_AGE_OFFSET, 80
Вызов структуры на си :
struct person p;
на асме :
foo:
#Standard header beginning
pushl %ebp
movl %esp, %ebp
#Reserve our local variable
subl $PERSON_SIZE, %esp
#This is the variable's offset from %ebp
.equ P_VAR, 0 - PERSON_SIZE
#Do Stuff Here
#Standard function ending
movl %ebp, %esp
popl %ebp
ret
Доступ к элементам структуры : си
p.age = 30;
асм:
movl $30, P_VAR + PERSON_AGE_OFFSET(%ebp)
Указатели
Си:
int global_data = 30;
a = &global_data;
Асм:
.section .data
global_data:
.long 30
movl $global_data, %eax
Локальные переменные - си :
void foo()
{
int a;
int *b;
a = 30;
b = &a;
*b = 44;
}
Асм:
foo:
#Standard opening
pushl %ebp
movl %esp, %ebp
#Reserve two words of memory
subl $8, $esp
.equ A_VAR, -4
.equ B_VAR, -8
#a = 30
movl $30, A_VAR(%ebp)
#b = &a
movl $A_VAR, B_VAR(%ebp)
addl %ebp, B_VAR(%ebp)
#*b = 30
movl B_VAR(%ebp), %eax
movl $30, (%eax)
#Standard closing
movl %ebp, %esp
popl %ebp
ret
Для конвертации си-кода в асм можно использовать команду :
gcc -S file.c
Использование дебаггера
Для того чтобы можно было отдебажить программу , ее нужно откомпилировать с опцией дебага:
as --gstabs prog.s -o prog.o
Теперь в каталоге , где лежат и обьектный , и исходный файл , можно запустить команду :
gdb ./prog
Теперь набираем
run
Если программа подвисает , можно нажать control-c .
Можно выполнять пошаговую команду
stepi
Можно вывести содержимое регистров командой
info register
или для одного регистра
print/$eax
Можно расставить точки прерывания . Перед тем , как запустить команду run , можно выполнить
break 27
и после запуска run программа остановится в 27 строке .
Для вывода кода можно использовать команду
l
Можно установить break на функцию , вызвав команду
break имя_функции
Можно вместо команды stepi вызвать
nexti
|