الحالة الأولية لتسجيلات البرنامج والمكدس على Linux ARM

StackOverflow https://stackoverflow.com/questions/1802783

  •  05-07-2019
  •  | 
  •  

سؤال

ألعب حاليًا بتجميع ARM على نظام Linux كتمرين تعليمي.أنا أستخدم التجميع "العاري"، أي.لا يوجد libcrt أو libgcc.هل يمكن لأي شخص أن يوجهني إلى معلومات حول الحالة التي سيكون عليها مؤشر المكدس والسجلات الأخرى في بداية البرنامج قبل استدعاء التعليمات الأولى؟من الواضح أن نقاط pc/r15 عند _start، ويبدو أن الباقي تمت تهيئته إلى 0، مع استثناءين؛يشير sp/r13 إلى عنوان بعيد خارج برنامجي، ويشير r1 إلى عنوان أعلى قليلاً.

إذن لبعض الأسئلة القوية:

  • ما هي القيمة في r1؟
  • هل القيمة في sp مكدس شرعي مخصص بواسطة النواة؟
  • إذا لم يكن الأمر كذلك، ما هي الطريقة المفضلة لتخصيص المكدس؛باستخدام brk أو تخصيص قسم .bss ثابت؟

كل التقدير لكل النصائح.

هل كانت مفيدة؟

المحلول

إليك ما أستخدمه لبدء برنامج Linux/ARM مع المترجم الخاص بي:

/** The initial entry point.
 */
asm(
"       .text\n"
"       .globl  _start\n"
"       .align  2\n"
"_start:\n"
"       sub     lr, lr, lr\n"           // Clear the link register.
"       ldr     r0, [sp]\n"             // Get argc...
"       add     r1, sp, #4\n"           // ... and argv ...
"       add     r2, r1, r0, LSL #2\n"   // ... and compute environ.
"       bl      _estart\n"              // Let's go!
"       b       .\n"                    // Never gets here.
"       .size   _start, .-_start\n"
);

كما ترون، أنا فقط أحصل على عناصر argc وargv والبيئة من المكدس في [sp].

توضيح بسيط:يشير مؤشر المكدس إلى منطقة صالحة في ذاكرة العملية.r0 وr1 وr2 وr3 هي المعلمات الثلاثة الأولى للوظيفة التي يتم استدعاؤها.أقوم بملءها بـ argc و argv وEnviron على التوالي.

نصائح أخرى

نظرًا لأن هذا هو Linux، يمكنك إلقاء نظرة على كيفية تنفيذه بواسطة النواة.

يبدو أن السجلات يتم ضبطها عن طريق الاتصال بـ start_thread في نهاية load_elf_binary (إذا كنت تستخدم نظام Linux حديثًا، فسيستخدم تنسيق ELF دائمًا تقريبًا).بالنسبة لـ ARM، يبدو أن السجلات قد تم ضبطها على النحو التالي:

r0 = first word in the stack
r1 = second word in the stack
r2 = third word in the stack
sp = address of the stack
pc = binary entry point
cpsr = endianess, thumb mode, and address limit set as needed

من الواضح أن لديك مكدسًا صالحًا.أعتقد أن القيم r0-r2 هي غير هامة، ويجب عليك بدلاً من ذلك قراءة كل شيء من المكدس (سترى لماذا أعتقد ذلك لاحقًا).الآن، دعونا نلقي نظرة على ما هو موجود على المكدس.ما ستقرأه من المكدس يتم ملؤه create_elf_tables.

أحد الأشياء المثيرة للاهتمام التي يجب ملاحظتها هنا هو أن هذه الوظيفة مستقلة عن البنية، لذا سيتم وضع نفس الأشياء (في الغالب) على المكدس في كل بنية Linux قائمة على ELF.ما يلي موجود على المكدس، بالترتيب الذي ستقرأه به:

  • عدد المعلمات ( argc في main()).
  • مؤشر واحد إلى سلسلة C لكل معلمة، متبوعًا بصفر (هذه هي محتويات argv في main(); argv سيشير إلى أول هذه المؤشرات).
  • مؤشر واحد إلى سلسلة C لكل متغير بيئة، متبوعًا بصفر (هذه هي محتويات الملف الذي نادرًا ما يُرى envp المعلمة الثالثة main(); envp سيشير إلى أول هذه المؤشرات).
  • "المتجه المساعد"، وهو عبارة عن سلسلة من الأزواج (نوع متبوع بقيمة)، منتهية بزوج بصفر (AT_NULL) في العنصر الأول.يحتوي هذا المتجه المساعد على بعض المعلومات المثيرة للاهتمام والمفيدة، والتي يمكنك رؤيتها (إذا كنت تستخدم glibc) عن طريق تشغيل أي برنامج مرتبط ديناميكيًا باستخدام الملف LD_SHOW_AUXV تم تعيين متغير البيئة على 1 (على سبيل المثال LD_SHOW_AUXV=1 /bin/true).هذا هو المكان الذي يمكن أن تختلف فيه الأشياء قليلاً اعتمادًا على الهندسة المعمارية.

وبما أن هذا الهيكل هو نفسه بالنسبة لكل معمارية، يمكنك أن تبحث على سبيل المثال في الرسم الموجود في الصفحة 54 من سيسف 386 أبي للحصول على فكرة أفضل عن كيفية تناسب الأشياء معًا (لاحظ، مع ذلك، أن ثوابت نوع المتجهات المساعدة في تلك الوثيقة تختلف عما يستخدمه Linux، لذا يجب عليك إلقاء نظرة على ترويسات Linux الخاصة بها).

الآن يمكنك أن ترى لماذا محتويات r0-r2 هي القمامة.الكلمة الأولى في المكدس هي argc, والثاني هو مؤشر لاسم البرنامج (argv[0])، والثالث ربما كان صفرًا بالنسبة لك لأنك اتصلت بالبرنامج بدون وسائط (سيكون argv[1]).أعتقد أنهم تم إعدادهم بهذه الطريقة لكبار السن a.out تنسيق ثنائي، والذي كما ترون في create_aout_tables يضع argc, argv, ، و envp في المكدس (حتى ينتهي بهم الأمر في r0-r2 بالترتيب المتوقع لإجراء مكالمة إلى main()).

وأخيرا، لماذا كان r0 صفر لك بدلاً من واحد (argc يجب أن يكون واحدًا إذا اتصلت بالبرنامج بدون وسائط)؟أعتقد أن شيئًا ما عميقًا في آلية syscall قد قام بالكتابة فوقه بالقيمة المرجعة لاستدعاء النظام (والتي ستكون صفرًا منذ نجاح exec).يمكنك أن ترى في kernel_execve (الذي لا يستخدم آلية syscall، نظرًا لأنه ما تستدعيه النواة عندما تريد تنفيذ الأمر exec من وضع kernel) والذي يقوم بالكتابة فوقه عمدًا r0 مع قيمة الإرجاع do_execve.

هنا uClibc crt.يبدو أنه يشير إلى أن كافة السجلات غير محددة باستثناء r0 (الذي يحتوي على مؤشر دالة ليتم التسجيل به atexit()) و sp الذي يحتوي على عنوان مكدس صالح.

إذن القيمة التي تراها r1 ربما لا يكون شيئًا يمكنك الاعتماد عليه.

يتم وضع بعض البيانات على المكدس من أجلك.

لم أستخدم ARM Linux مطلقًا ولكني أقترح عليك إما إلقاء نظرة على مصدر libcrt ومعرفة ما يفعلونه، أو استخدام gdb للدخول إلى ملف قابل للتنفيذ موجود.لا يجب أن تحتاج إلى الكود المصدري، فقط قم بالخطوة خلال كود التجميع.

كل ما تحتاج إلى معرفته يجب أن يحدث داخل الكود الأول الذي يتم تنفيذه بواسطة أي ثنائي قابل للتنفيذ.

أتمنى أن يساعدك هذا.

توني

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top