Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Languages
 С
 GNU С Library 
 Qt 
 STL 
 Threads 
 C++ 
 Samples 
 stanford.edu 
 ANSI C
 Libs
 LD
 Socket
 Pusher
 Pipes
 Encryption
 Plugin
 Inter-Process
 Errors
 Deep C Secrets
 C + UNIX
 Linked Lists / Trees
 Asm
 Perl
 Python
 Shell
 Erlang
 Go
 Rust
 Алгоритмы
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Secure Programming for Li...6507 
 Linux Kernel 2.6...5279 
 Trees...1119 
 Максвелл 3...1051 
 William Gropp...990 
 Go Web ...963 
 Ethreal 3...930 
 Ethreal 4...918 
 Gary V.Vaughan-> Libtool...914 
 Ext4 FS...905 
 Clickhouse...901 
 Rodriguez 6...900 
 Ethreal 1...897 
 Steve Pate 1...886 
 C++ Patterns 3...864 
 Assembler...854 
 Ulrich Drepper...844 
 DevFS...788 
 MySQL & PosgreSQL...774 
 Стивенс 9...758 
 
  01.01.2024 : 3621733 посещений 

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

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

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