Sirous
Sirous
خواندن ۳ دقیقه·۵ سال پیش

hello world بدون libc

خیلی‌ها ممکنه علاقع داشته باشن بدونن اگر توی یه برنامه رو از کتاب‌خونه‌های استاندارد استفاده نکنیم چی میشه؟

برای این که یه برنامه رو با gcc بدون libc کامپایل کنیم، باید از این دستور استفاده کنیم:

‍‍gcc main.cpp -nostdlib -m32

وقتی این جوری کامپایل کنیم دیگه لزومی به وجود تابع main نیست و به جاش برنامه از start_ شروع میشه.

می‌دونیم که برای چاپ hello world باید msg رو توی stdout بنویسیم. stdout برای هر برنامه‌ای فایل شماره ۱ه. پس باید این سیستم‌کال رو اجرا کنیم:

write(1, msg, sizeof(msg) - 1);

آخر تابع start هم باید سیستم‌کال exit رو اجرا کنیم:‍‍

exit(0);

ولی ما نمی‌خوایم از این تابع‌ها هم استفاده کنیم D: به خاطر همین اونا رو هم خودمون می‌نویسیم. توی لینوکس همه سیستم‌کال‌ها توسط اینتراپت شماره 0x80 اجرا می‌شن در واقع هندلر اینتراپت ۸۰ توی کرنل کارش اجرا کردن سیستم‌کال‌هاست. برای اجرای سیستم‌کال‌ها باید آرگمان‌های مورد نیازشونو رو توی رجیستر‌های پردازنده قرار بدیم و بعدشم دستور int $0x80 رو اجرا کنیم. نکته دیگه اینه که برنامه در انتها باید سیستم‌کال exit رو هم اجرا کنه. تو جدول زیر هم میشه چند تا سیستم‌کال خیلی پرکاربرد و به همراه شمارشون و رجیستر‌های ارگمان‌هاشون رو دید(منبع: https://www.tutorialspoint.com/assembly_programming/assembly_system_calls.htm):

همون‌طور که توی جدول معلومه شماره سیستم‌کال باید توی رجیستر eax باشه برای write این شماره 4ه و برای exit این شماره 1ه. برای سیستم‌کال exit اول باید مقدار 1 رو توی رجیستر eax بذاریم و بعدشم 0 رو توی ebx بذاریم. در انتها باید درخواست اینتراپت 0x80 رو بدیم. پس exit(0) این طوری میشه:

asm("movl $1,%eax;" "xorl %ebx,%ebx;" "int $0x80");

برای write هم باید 4(شماره سیستم‌کال از جدول بالا) رو توی eax بذاریم، بعدش ebx باید 1 باشه (شماره فایلی که همیشه مربوط به stdoutه)، آدرس بافر (اینجا msg) رو توی ecx و طول بافر رو توی edx. دو تا تغییر آخر نیاز به اکستنشن‌های اسمبلی gcc دارن. برای استفاده از اکستنشنا باید نتیجه‌هایی که می‌خوایم با متغییرا جایگزین بشن رو به ترتیب با %0، %1، %2 و ... مشخص کنیم و برای رجیسترا هم یه % اضافه بذاریم و بعد از دستور باید به ترتیب لیست متغییرایی که باید نتیجه در اون‌ها قرار بگیره ،لیست متغیرایی که باید از مقدارشون استفاده بشه و لیست رجیسترایی که نباید از اون‌ها استفاده بشه رو مشخص کنیم:

asm("movl %0, %%ecx"::"r"(msg):); asm("movl %0, %%edx"::"r"(sizeof(msg)-1):"%ecx");

توی خط اول داریم می‌گیم msg رو بذار توی ecx. اون دو تا : هم برای اینه که لازم نبوده چیزی رو از رجیسترا بذاریم توی متغیرا. بعد : سوم هم، رجیسترایی که نمی‌خوایم مقدارشون تغییر کنه رو می‌نویسیم که اینجا خالیه. توی خط دوم هم طول msg رو می‌ذاریم توی edx این جا چون مقدار ecx برامون مهمه و نمی‌خوایم تغییر کنه بعد : سوم قرارش می‌دیم.
اخرش فایل main.cpp این شکلی میشه:

extern"C" void _start() { const char msg[] = "helloworld!"; asm("movl %0, %%ecx"::"r"(msg):); asm("movl %0, %%edx"::"r"(sizeof(msg)-1):"%ecx"); asm("movl $4, %eax;" "movl $1, %ebx;" "int $0x80"); asm("movl $1, %eax;" "xorl %ebx, %ebx;" "int $0x80"); }

و اینم از نتجه:

شاید از این پست‌ها خوشتان بیاید