السلام عليكم ورحمة الله وبركاته
إذا أردت كتابة برنامج أسمبلي للينكس لطباعة رسالة مثلاً فربما تلجأ لإستدعاءات النظام system calls. مثال لبرنامج يطبع رسالة:
.data
message: .string "Hello world!\n"
.text
.globl _start
_start:
movl $4, %eax /* رقم مقاطعة الكتابة */
movl $1, %ebx /* stdoutالكتابة لـ */
movl $message, %ecx /* مؤشر للنص */
movl $13, %edx /* طول النص */
int $0x80 /* إستدعاء النواة */
movl $1, %eax /* رقم مقاطعة الخروج */
movl $0, %ebx /* رقم خروج البرنامج */
int $0x80 /* إستدعاء النواة */تشغيله:
$ as asmexample.asm -o asmexample.o # قمنا بتجميع البرنامج لينتج ملف أوبجكت $ ld asmexample.o -o asmexample # قمنا بربط ملف الأوبجكت $ ./asmexample # شغلنا البرنامج Hello world! # طُبعت رسالتنا $ echo $? # يفترض أن يكون رقم الخروج 0 0 $
جميل. لكن ماذا لو أردنا إستدعاء دالة من مكتبة خارجية بدلاً من إستخدام دوال النظام؟. مثلاً نريد إستدعاء الدالة printf من مكتبة libc (مكتبة الـC القياسية)؟ الموضوع لم يكن بديهي واجهت بعض الصعوبات عند محاولتي عمل هذا.
لننظر للبرنامج هذا:
.section .data
message: .string "Hello world!"
format: .string "%s %d\n"
.section .text
.globl _start
_start:
/* printf(format, message, 123); */
movl $123, %ebx /* نقل الرقم 123 للمسجل */
pushl %ebx /* دفع المتغير الثالث للدالة */
pushl $message /* دفع المتغير الثاني للدالة */
pushl $format /* دفع المتغير الأول للدالة */
call printf /* إستدعاء الدالة */
movl $1, %eax
movl $0, %ebx
int $0x80برنامج يتدعي الدالة printf لطباعة رسالة. لنقم بتصريفه:
$ as asmexample.asm -o asmexample.o $ ld asmexample.o -o asmexample asmexample.o: In function `_start': (.text+0x11): undefined reference to `printf' $
الربط لم ينجح ويخبرك بأنه لم يجد printf. طبعاً هذا لأن printf موجودة في مكتبة libc لذا فنحتاج للربط معها. يمكن عمل هذا بتمرير الخيار lc- للـlinker:
$ as asmexample.asm -o asmexample.o $ ld asmexample.o -o asmexample -lc $ ./asmexample bash: ./asmexample: No such file or directory $
التصريف نجح إلا أن هناك خطأ عن شيء مفقود. ولم أعرف مالذي ينقص. لنأخذ برنامج الـC هذا ونصرفه لمعرفة السبب:
$ cat cexample.c
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello world!\n");
return 0;
}
$ gcc cexample.c -o cexample
$ ./cexample
Hello world!
$الآن "الخطوة الأهم" وهي معرفة مكتبات الـshared object التي يرتبط بها البرنامج بإستخدام أداة ldd. الـshared object في يونكس تكافئ مكتبات الـdll في ويندوز. وأداة ldd تشبه أداة dependency walker في ويندوز:
$ ldd cexample linux-gate.so.1 => (0xb788c000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb773a000) /lib/ld-linux.so.2 (0xb788d000) $
قارن الناتج مع برنامجنا الأول:
$ ldd asmexample linux-gate.so.1 => (0xb788e000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb773c000) /usr/lib/libc.so.1 => /lib/ld-linux.so.2 (0xb788f000) $
يلزمنا أن نربط برنامجنا مع المكتبة ld-linux.so.2 بعينها. لتحديد المكتبة التي تريد الربط معها فلن تستخدم الخيار l- بل الخيار I- (أو )متبوع بمسار المكتبة الكامل ستجده في دليل ld بكتابة man ld:
$ man ld
...
-Ifile
--dynamic-linker=file
Set the name of the dynamic linker. This is only meaningful when generating dynamically linked ELF executables. The default dynamic linker
is normally correct; don't use this unless you know what you are doing.
...
$لنجرب:
$ as asmexample.asm -o asmexample.o $ ld asmexample.o -o asmexample -lc -I /lib/ld-linux.so.2 $ ./asmexample Hello world! 123 $
جميل. للنظر للمكتبات:
$ ldd asmexample linux-gate.so.1 => (0xb77a4000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7652000) /lib/ld-linux.so.2 (0xb77a5000) $ ldd cexample linux-gate.so.1 => (0xb77e6000) libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7694000) /lib/ld-linux.so.2 (0xb77e7000) $
كلا البرنامجين متطابقان الآن.
بعض تجاربي دوال C:
.section .data
message: .string "Hello world!"
format: .string "%c\n"
.section .text
.globl _start
_start:
/**
edi = 0;
ebx = 0;
loop:
ebx = *(message+edi);
printf(format, edx);
sleep(1);
edi++;
if( *(message+edi) != 0 )
goto loop
syscall(1, 0);
**/
xorl %edi, %edi
xorl %ebx, %ebx
loop:
movb message( , %edi, 1), %bl
pushl %ebx
pushl $format
call printf
pushl $1
call sleep
inc %edi
cmpb $0, message( , %edi, 1)
jne loop
movl $1, %eax
movl $0, %ebx
int $0x80.section .data
filename: .string "file.txt"
filemode: .string "w"
message : .string "Hello world\n"
errmsg : .string "Error"
.section .text
.globl _start
_start:
/**
eax = fopen(filename, filemode);
if( eax == 0 )
goto error
edi = eax;
fprintf(edi, message);
fclose(edi);
syscall(1, 0);
error:
perror(errmsg);
syscall(1, -1);
**/
pushl $filemode
pushl $filename
call fopen
cmpl $0, %eax
je error
movl %eax, %edi
pushl $message
pushl %edi
call fprintf
pushl %edi
call fclose
movl $1, %eax
movl $0, %ebx
int $0x80
error:
pushl $errmsg
call perror
movl $1, %eax
movl $-1, %ebx
int $0x80بالتوفيق