Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

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
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье