11.6. Переносимая программа для UNIX

Как вы могли заметить, отличия между программами на ассемблере для раз­ных UNIX-систем сводятся к разнообразным способам вызова системных функ­ций. Кроме того, следует заметить, что номера этих функций могут тоже отличать­ся. Рассмотрим на примере простой программы - аналога системной команды

pwd, как эти различия можно учесть.

Мы начнем создавать программу pwd без использования системных библиотек. В таком виде она может быть полезна как, например, часть мини-UNIX на одной дискете. Как мы позднее убедимся, размер нашей версии pwd станет меньше такой же программы на С (в 50-100 раз) без учета того, что любой программе на С до­полнительно нужна библиотекаlibcso, занимающая сотни килобайт.

Команда pwd должна Всего лишь вывести на стандартный вывод полное имя

текущего каталога. Для этого можно воспользоваться различными алгоритмами: 1. Системный вызов getcwd(), который присутствует в Linux 2.2 (номер 183)

и FreeBSD 3.0 (номер 326). Этой функции передается адрес буфера и его раз­мер, а она записывает в буфер полное имя текущего каталога. Данный способ -самый эффективный, и мы в первую очередь будем использовать именно его.

2. Специальный файл в Linux 2.2 является символической ссылкой на текущий каталог того процесса, который проверяет ее.

3. Некоторые интерпретаторы передают запускаемым программам значение те­кущего каталога в переменной среды PWD. Такой способ - самый ненадежный, потому что под другим shell нашу программу нельзя будет запустить.

4. Классический алгоритм, в котором программа поэтапно исследует содержи­мое вышележащего каталога (../, затем ../.. / и т. д.) и находит запись, совпа­дающую с предыдущим рассмотренным каталогом (./,../, и т. д.). Такой ал­горитм мы будем использовать, если в системе не поддерживаются ни системный вызов getcwdQ, ни специальный файл /proc/self/cwd.

Поскольку имена большинства системных вызовов разнятся, разместим их в от­дельном файле config.i, который будет включен в программу директивой .include:

// Способ обращения к системному вызову (установить

// SYSCALLJinux в 1,   если используется int $0x80, и // = 1, если используется

SYSCALL^linux = О SYSCALL„unix = 1

// Максимальная длина пути (из

МАХ^РАТН = 4096

// Стратегия для

// 1, если используется системный вызов PWD_sys = 1

// 1, если используется файл /proc/self/cwd, PW0_p.roc = О

// 1, если используется обычный (переносимый) алгоритм, PWD_posix = 0 ■

// 1,  если используется переменная среды PW0. PWD_env = 0

// Номера используемых системных вызовов (из sys/syscalls. h):

// exit() - всегда 1.

SYSCALL_EXIT = 1

// write() - всегда 4.

SYSCALLJIRITE = 4

// open()  - всегда 5.

SYSCALL_0PEN = 5

// close() - всегда 6.

SYSCALL^CLOSE = 6

// readHnkO - 58 на FreeBSD, 85 на Linux. SYSCALL_READLINK = 58

// readdirO - нет на FreeBSD,  89 на Linux. SYSCALL^READDIR = 0

// getdentsf) - нет на FreeBSD,   141 на Linux. SYSCALL.GETDENTS = 0

// getdirentries() - 196 на FreeBSD, нет на Linux. SYSCALL_GETDIRENTRIES = 196

// stat() - 188 на FreeBSD,  106 на Linux. SYSCALL_STAT = 188

// lstat() - 190 на FreeBSD,  107 на Linux. SYSCALL_LSTAT = 190

// fstat()  - 189 на FreeBSD,   108 на'Linux. SYSCALL_FSTAT =189

// getcwdO - 326 на FreeBSD 3.0,   183 на Linux 2.2, // нет на старых версиях. SYSCALL_GETCWD = 326

// Размеры используемых структур. // Размер struct direnf. // 1024 на FreeBSD,  266 на Linux. SIZE_DIRENT = 1024

// Смещение dirent.ino от начала структуры. DIRENT_INO = О

// Смещение dirent.len от начала структуры: // 4 на FreeBSD,  8 на Linux. DIRENT_IEN = 4

// Смещение dirent.name от начала структуры:

// 8 на FreeBSD, 10 на Linux.

DIRENT_NAME = 8

// Размер структуры stat и смещения ее

// dev,  ino и nlink.

SIZE_STAT = 64

ST_0EV = О

ST_INO =4

SOLINK = 10

Для всех пунктов этого файла можно написать сценарий с целью автоматичес­кого конфигурирования, аналогичный GNU autoconfigure, при использовании ко­торого модификация вручную будет не нужна. Этот сценарий, а также другие про­стые программы на ассемблере для UNIX доступны в Internet по адресу: http:// www.Iionking.org/-cubbi/serious/asmix.html.

Теперь, когда все возможные различия между версиями системы предусмотрены, перейдем непосредственно к программе. Упрощая задачу, не станем обрабатывать со­общения об ошибках, но код возврата в случае ошибки всегда будет ненулевым.

// Включение файла config.i. .include "config.i"

// Начало программы. .globl _start _start:

.if PWD_posix

// Переносимая  (и сложная)  стратегия для pwd.

// В %ebp будет храниться указатель на первый символ части искомого пути, // определенной к настоящему моменту.

18 Assembler для DOSк корневому.

II Она заполняется справа налево - от текущего каталога

то\/1 $рг_ЬиГгег+1024-1,%еЬр II Искомый путь инициализируется символом перевода строки.

тоу1 $0х0А,(%еЬр) II В %еэ1 будет храниться указатель на путь вида "../../.

■шоч1 $ир_Ь^ег,%ез1 // инициализированный как "ДО".

то\м      $0хО02Е, up_buffeг II Выполнить згат-С'/").  чтобы определить 1пойе и йеу для корневого каталога // (по ним мы позже установим,  когда он будет достигнут). . if 5У5САИ__иП1Х

$зт._зтат $гоо!_ра1Г» ЗЗУБСАИ^БТАТДеах %еах

0х9А

pushl pusnl movl pushl . byte ■ long .word addl

.endif

.if SYSCALL^linux

.endif

movl movl movl

int

test!

и

0 7

$12,%esp

$st_stat,%ecx $root_path,%ebx $SYSCALL_STAT,%eax

$0x80

%eax,%eax error exit

// Сохранить dev и inode корневого каталога.

,movl     st„stat+ST OEV,%eax movl      %eax,root_dev movl st_stat+ST_INO,%eax movl %eax,root,ino

//

перемещение по каталогам.

Главный цикл main_posix_cycle:

// Вызвать lstat() для текущего каталога типа // чтобы определить его inode и dev. .if SYSCALL unix

pushl

$st_stat

pushl

$up_buffer

movl

$SYSCALL_LSTAT, %eax

pushl

%eax

. byte

Ox9A

.long

0

.word

7

addl

$12,%esp

Переносимая программа для ІШІХ

т

.if SYSCALL linux

movl $st_stat,%ecx -

movl $up_buffer,%ebx

movl $SYSCALL_LSTAT,%eax

int $0x80

.endif

Іезгі %еах,%еах зпг еггог^ехіі

// Сохранить inc.de и dev каталога, который сейчас будет сканироваться.

поті зг_зіат+!ЗТ_0ЕУ,%еах

шоуі %еах,йеу

тоуі 5і_зіаі+5Т„ІМ0,%еЬх

тоуі %еЬх,іпо // Проверить, не совпадает ли этот каталог с

стрі      %еах,гооХ^деч

jпe розіх_по^іпізИеЬ

стрі %еЬх,гооі_іпо

]'пе розіх_поі_ПпізпеЬ // Если совпадает - вписать последний символ

йесі %еЬр

ліоуЬ      $0х2Р,(%еЬр) // и вывести его на экран.

Ігор розіх_ИгИе

// Иначе -ровіх_по^ іпізЬесІ:

корневым.

"/" на левом конце искомого пути

// добавить -.ДО" к ир_Ьиііег,  чтобы перейти точи      $0х2Е2Е,(%езі) іпсі %езі

к вышележащему каталогу.

Дпсі

movb

tesi

$0,(%esi)

// Открыть данный

.if SYSCALL unix

каталог только для чтения <0_РШ(ЖУ).

pushl $0

pushl $up_buffer

movl $SYSCALLJ)PEN,%eax

pushl %eax

. byte 0x9A

.long 0

.word 7

addl $l2,%esp

.endif

. if SYSCALL linux

movl $0,%ecx

movl $up_buffer,%ebx

movl $SYSCALL_OPEN,%eax

int $0x80 '

Ассемблер 8 среде UNIX

testl

jl

movl'

// Выполнить fstat над .ifSYSCALL.unix

pushl pushl movl pushl .byte .long .word addl

.endif

.if SYSCALL_linux

movl movl movl int

testl error_exit

все в порядке, movb incl

.endif jnz

// Если

%eax,%eax

error.exit %eax,%edi

этим каталогом.

$st_stat %eax

$SYSCALL_FSTAT,%eax

leax 0x9A

0 7

$12,%esp

$st_stat,%ecx %eax,%ebx

$SYSCALl_FSTAT,%eax

$0x80

%eax,%eax

добавить 7" для $0x2F, <%esi) %esi

следующего каталога.

// •//

Вложенный цикл: рассмотреть каждую запись в вышележащем

каталоге и

сравнить xorl readdirecycle: // Вызов readdirO легче ,if SYSCALL READDIR .if SYSCALL_unix

pushl pushl pushl movl

pushl .byte . long .word • addl

ее inode и dev %ebx,%ebx

с текущей.

.endif

SYSCALL_linux

movl

movl

movl movl int

использовать, чем getdents().

$1

$dirent %edi

$SYSCALL_READDlMeax

%eax

0x9A

0

7

$16,%esp

$l,%edx Sdirent,%ecx %edi,%ebx

$SYSCALL READDIR,%eax

$0x80программа для UNIX

// В $ebx будет структура dirent, над которой мы 'работаем в настоящий момент. movl $dirent,Sebx decl Seax

jne notfound .else

// Если нет readdir(),  но есть getdents и getdirentries,   алгоритм усложняется, так

// как

они возвращают не

одну структуру dirent, 'а столько,   сколько поместится в буфер.

 

 

testl

«еЬхДеЬх

 

 

]Z •

time_to_getdent

 

 

xorl

%еах,%еах

 

 

movw

01ЯЕМТ_1ЕМ(%еЬх),%ах

 

 

addl.

%еах,%еЬх

// Проверить, выйдет ли

%еЬх за пределы буфера после добавления ІЖ

 

 

xorl

%еах,%еах

 

 

movw

РМЕЙТ 1ЕМ(%еЬх),%ах

 

 

cmpl

dirent_filled,%eax

jge

time_to_getdent

 

 

 

movw

01ЮТ_1ЕМ(%еЬх),%ах

 

 

testl

%еах,%еах

jnz

skip_getdent

 

time_to_getdent:

 

 

// Если

выходит

- пора

делать ^епіз() или getdirentries() -

// в за

ВИСИМОСТИ

от операционной

.if SYSCALL_unix

 

 

■.if SYSCALL_GETDENTS

-

 

 

pushl

$SIZE_DIRENT

 

 

pushl

$dirent

 

 

pushl

%edi

 

 

movl

$SYSCALL_GETDENTS, %eax

 

 

pushl

%eax

 

 

. byte

Ox9A

 

 

.long

0

 

 

.word

7

 

 

addl

$16,%esp

.else

 

 

 

 

 

pushl

$basep

 

 

pushl

$SIZE_DIRENT

 

 

pushl

$dirent

 

 

pushl

leii

 

 

movl

$SYSCALL_GETDIRENTRIES,%eax

 

 

pushl

%eax

 

 

. byte

Ox9A

 

 

.long

0

 

 

.word

7

 

 

addl

$20,%esp

.endif

 

 

 

.endif

 

 

 

.if SYSCALL limiX

.endif movl $SIZE,DIRENT,%edx

movl $dirent,%ecx

movl %edi,%ebx

movl $SYSCALL_GETDENTS,%eax

int $0x80

movl %eax,dirent_filled

testl %eax,%eax

je notfound

movl $dirent,%ebx

skip_getdent: .endif

// Скопировать

// в конец строки  . . / xorl . pushl pushl movl movl addl movw incl rep movsb popl 1 popl

// Выполнить lstat() для pushl

.if SYSCALL_unix

pushl pushl movl

pushl . byte . long . word

addl

сканируемом каталоге имя полученной /../../, не смещая указатель %есх,%есх %esi %edi

%esi,%edi %ebx,%esi $DIRENT_NAME, %esi DIRENT_LEN(%ebx),%cx %ecx

записи в %esi.

%edi %esi

этой записи, %ebx

чтобы получить ее inode и dev.

.endif

.if SYSCALL linux

.endif

movl movl movl

int

popl

testl

$st_stat

$up_buffer

$SYSCALL_LSTAT,%eax

%eax

0x9A

0

7

$12,%esp

$st_stat,%ecx

$up_buffer,%ebx

$SYSCALL_LSTAT,%eax

$0x80

%ebx

%eax,%eax

readdir_cycle

МММ!

// Если они не совпадают с сохраненными ino.de и dev для // текущего сканируемого,каталога - продолжить.

movl dev,%eax

cmpl %еах,st stat+ST DEV

jne readdir_cycle

movl ino,%eax

cmpl 9teax,st stat+ST INO

jne readdir.cycle

// Вернуть up buffer в состояние ../../../../../, соответствующее вышележащему каталогу.

movl $0,(%esi)

// Добавить 7" в создаваемый путь pwd (кроме случая, если это был самый первый каталог).

cmpb $1, first

je not_first

decl %ebp

movb $0x2F,(lebp)

not_first :

movb $0,first ■

// Сдвинуть указатель в %ebp на namlen байтов влево, xorl

.if SYSCALL_READDIR

%edx,%edx DIRENT__LEN(%ebx),%dx

. else

seek zero:

pushl

xorl

movl addl

incl

movb

testb

jnz

.endif

seek zero

movl

popl

%esi

%ecx,%ecx %ebx,%esi $DIRENTJJAME,%esi

%ecx

(%esi,%epx,l),%al %al,*al

%ecx,%edx %esi

subl %edx,%ebp

// Скопировать имя найденного каталога в pwd,  не сдвигая указатель. xorl %есх,%есх tesi %edi

%ebp,%edi %ebx,%esi SDIRENT NAME,%esi

pushl pushl

movl

movl

addl

.if SYSCALL_READDIR movw

movl

.endif

rep

DIRENT_LEN(%ebx),%CX %edx,%ecx

movw

Ассемблер в среде иМХ

movsb

popl %edi popl %esi

// Закрыть открытый каталог, .if SYSCALL_unix

pushl movl

pushl .byte .long .word addl

.endif

.if SYSCALL_linux

movl movl int

%edi.

$SYSCALL_CLOSE,%eax %eax

Gx9A

0

7

$8,%esp

tedi,%ebx

$SYSCALL_CLOSE,%eax

$0x80

.endif

// Продолжить notfound:

posix_write:

// Вывести на

главный цикл,  пока не будет }тр та1п_роз1х_сус1е

]тр еггог_ех11

экран найденный путь.

достигнут корневой каталог.

.if SYSCALL unix movl subl

movl

xorl incl

pushl

pushl

pushl

movl

pushl

. byte

.long . word

addl

. endif

.if SYSCALL linux

movl int

$pt_buffer+1024,%edx

%ebp,%edx

%ebp,%ecx

%ebx,%ebx

%ebx

%edx %ecx %ebx

$SYSCALL_WRITE,teax %eax

Gx9A

0

7

$l6,%esp

$SYSCALL_WRITE,%eax

$0x80

.endif .endif

// Конец стратегии Р1^0_роз1х.

// распечатать значение

.if PWD_env

eld переменной среды Р1аГО (плохая стратегия).рор1 %еЬх

шоу1 4(%езр,%еЬх,4),%е01

// Теперь адрес списка переменных в 96е<1±. . хог1 %еах,%еах

// Сканирование переменных в поисках той, начало которой совпадет с епу_пате. епу 8сап_сус1е:

$0,X%edi)

cmpl error_exit

movl movl rape cmpsb

jcxz

xoгl

decl

гepne

scasb

jmp

$env_name,%esi $env name l,%ecx

found_variable %ecx,%ecx

%ecx

env_scan_cycle

// Теперь %edi foundj/ariable:

xorl decl

// Найти его длину loop_zero_scan:

incl cmpb jne movb

incl

// и вывести на экран, .if SYSCALL_unix

pushl pushl

pushl

movl

pushl

. byte

.long

.word

addl

.endif

.if SYSCALL_linux

movl movl movl

int

.endif .endif

адрес значения переменной PWD,  заканчивающийся нулем.

%edx,

%edx

%edx

$0, (%edi,%edx, 1)

loop_zero_scan $0x0A,(%edi,%edx,1)

%edx

%edx %edi $1

$SYSCALL_WRITE,%eax

%eax

Gx9A

0

7

$16,%esp

%edi,%ecx $1,%еЬх

$SYSCALL_WRITE,%eax $0x80

// Стратегия ргос - выводится имя каталога, на который // указывает символическая ссылка /рroc/self/cwd. .if PWD_proc

// Вызвать readlink(7proc/self/cwd").

movl $255,%edx

movl $linux_buffer,%ecx

movl $linux_pwd,%ebx

movl       $SYSCALL_READLINK,%eax

// Эта стратегия осуществима только в Linux, int $0x80

.endif

// Стратегия .if PWD_sys

// Вызвать

.if SYSCALLjnix

pushl pushl

movl

pushl

.byte .long f .word addl

.endif

.if SYSCALL_linux

movl movl movl int

с использованием системного вызова.

$MAX_PATH $linux_buffer $SYSCALL_GETCWD,%eax %eax

Gx9A

0

7

$12,%esp

.endif

testl js

decl

// В случае с FreeBSD надо .if SYSCALL_unix

xorl

$MAX_PATH,%ecx $linux_buffer,%ebx $SYSCALL_GETCWO,%eax

$0x80

%eax,%eax

error_exit

%eax

дополнительно

определить длину  возвращенной строки.

xorl

decl

movl

гepne scasb subl

movl incl

%eax,%eax %ecx,%ecx %ecx

$linux_buffer,%edi

$linux_buffer,%edi

%edi,%eax

%eax

.endif .endif

// Вывод на экран для стратегий ргос и зуэ. // Добавить символ новой строки в. конце.

.if PWD.proc

PWD_sys movb incl

$0x0A,linux_buffer(%eax)

%eax

// Вывести на экран linux„buffer. .if SYSCALL unix

pushl %eax

pushl $linux_buffer

pushl $1

movl $SYSCALL_WRITE,%eax

pusbl %eax

.byte 0x9A ,

.long 0

.word 7

addl $l6,%esp

.endif

.if SYSCALL_linux

movl %eax,%edx xorl %ebx,%ebx ■  movl $linux_buffer,%ecx incl %ebx

movl $SYSCALL,WRITE,%eax

int $0x80

.endif .endif

// Выход из программы без ошибок exit (О)-, exit:

xorl

.if SYSCALL junix

pushl movl pushl

. byte

■ long

.word

.endif

SYSCALL_linux

movl

int

.endif

%ebx,%ebx tebx

$SYSCALL_EXIT,%eax

%eax Ox9A 0 1

$SYSCALL_EXIT,%eax

$0x80

// Выход из программы .if "(-PWD_proc) exit_errno: exit_ENOENT: error_exit: .if SYSCALL_unix.

•pushl

movl

с ошибками.

$1

SSYSCALL EXIT,%eax

pushl %eax

. byte Gx9A

.long 0

.word 7

.endif

.if SYSCALL linux

xorl %ebx,%ebx

incl tebx

movl $SYSCALL^EXIT,%eax

int $0x80

.endif ,endif

// Область данных! .data

.if PWD_posix

// Текущий каталог для stat(). pwd_path:

.as.cii ".\000"

// Корневой каталог для stat().

root_path:

.ascii V\000-

// Флаг, нужный для того, чтобы не ставить "/" перед первым элементом пути, first:

. byte 1

.endif

// Имя файла в /ргос для readlink(). .if PWD_proc linux_pwd:

.ascii "/proc/self/cwd\000"

.endif

// Переменная среды для стратегии

.if PWD_env env_name:

.ascii "PW0=" env_name_l = . -env_name, .endif

// Указатель для системного вызова .if SYSCALL_GETDIRENTRIES

basep:

.long 0

.endif .bss

// Буфер для stat(). .if PWD^posix

.Icomm   st_stat,SIZE_STAT

// Буфер для результата работы getcwd() и readlink(). .if PWD_proc I PWD_sys

.lcomm   linux_buffer,MAX_PATH

.endif

.if PWD_posix

// Буфер для вывода pwd.

.lcomm   pt_buffer, 1024 // Буфер для сканируемого каталога ( ../../../../).

.lcomm   up_buffer, 1024

.endif

// Различные переменные, .if PWD_posix

.lcomm root_dev,4

.lcomm   dev.4

.lcomm root_ino,4

.lcomm ino,4

.lcomm bpt,4 // Буфер для записи из каталога (dirent).

.lcomm   dirent, SIZE_DIRENT .if SYSCALL_GETDENTS | SYSCALL_GETDIRENTRIES

.lcomm   dirent_filled, 4

.endif

.endif N

Для компиляции этой программы на любой системе достаточно двух команд: as -о pwd .о pwd. s Id -о pwd pwd.о

Полученная версия pwd занимает:

□ на Linux 2.2 - 428 байт (системная версия - 19 332 байта);

□ на Linux 2.0 - 788 байт (системная версия - 18 440 байт);

а на FreeBSD 3.1 - 484 байта (системная версия - 51 916 байт);

□ на FreeBSD 2.2 - 8192 байта (системная версия - 45 056 байт).

Если на FreeBSD 2.2 установлена поддержка запуска ELF-программ, можно скомпилировать работающий на ней файл pwd длиной 940 байт, но в любом слу­чае эти размеры впечатляют. Более того, для работы нашей версии pwd не требу­ется никакая библиотека типа libc.so - это полностью самостоятельная статичес­кая программа. Такие программы, написанные целиком на ассемблере, не только отличаются сверхмалыми размерами (в 20-100 раз меньше аналогов на С); но

и работают быстрее, потому что обращаются напрямую к ядру системы.