تشغيل رمز التجميع 32 بت على معالج Linux 64 بت ومعالج 64 بت: اشرح الشذوذ

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

سؤال

أنا في مشكلة مثيرة للاهتمام. لقد نسيت أنني أستخدم آلة ونظام التشغيل 64 بتات وكتبت رمز تجميع 32 بت. لا أعرف كيف أكتب كود 64 بت.

هذا هو رمز التجميع x86 32 بت لـ GNU Assembler (AT&T Syntax) على Linux.

//hello.S
#include <asm/unistd.h>
#include <syscall.h>
#define STDOUT 1

.data
hellostr:
    .ascii "hello wolrd\n";
helloend:

.text
.globl _start

_start:
    movl $(SYS_write) , %eax  //ssize_t write(int fd, const void *buf, size_t count);
    movl $(STDOUT) , %ebx
    movl $hellostr , %ecx
    movl $(helloend-hellostr) , %edx
    int $0x80

    movl $(SYS_exit), %eax //void _exit(int status);
    xorl %ebx, %ebx
    int $0x80

    ret

الآن ، يجب أن يعمل هذا الرمز بشكل جيد على معالج 32 بت ونظام التشغيل 32 بت صحيح؟ كما نعلم أن 64 بت معالجات متوافقة مع معالجات 32 بت. لذلك ، لن يكون هذا أيضًا مشكلة. تنشأ المشكلة بسبب الاختلافات في مكالمات النظام وآلية الاتصال في نظام التشغيل OS و 32 بت 64 بت. لا أعرف لماذا لكنهم غيروا أرقام مكالمات النظام بين Linux 32 بت و 64 بت.

يحدد ASM/unistd_32.h:

#define __NR_write        4
#define __NR_exit         1

ASM/UNISTD_64.H يحدد:

#define __NR_write              1
#define __NR_exit               60

على أي حال باستخدام وحدات الماكرو بدلاً من الأرقام المباشرة. ضمان أرقام استدعاء النظام الصحيحة.

عندما أقوم بتجميع وربط البرنامج.

$cpp hello.S hello.s //pre-processor
$as hello.s -o hello.o //assemble
$ld hello.o // linker : converting relocatable to executable

انها لا طباعة helloworld.

في GDB عرضه:

  • خرج البرنامج بالرمز 01.

لا أعرف كيف أقوم بالتصحيح في GDB. باستخدام البرنامج التعليمي ، حاولت تصحيحه وتنفيذ التعليمات عن طريق التحقق من التعليمات في كل خطوة. إنه يظهر لي دائمًا "البرنامج خرج مع 01". سيكون من الرائع أن يوضح لي البعض كيفية تصحيح هذا.

(gdb) break _start
Note: breakpoint -10 also set at pc 0x4000b0.
Breakpoint 8 at 0x4000b0
(gdb) start
Function "main" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Temporary breakpoint 9 (main) pending.
Starting program: /home/claws/helloworld 

Program exited with code 01.
(gdb) info breakpoints 
Num     Type           Disp Enb Address            What
8       breakpoint     keep y   0x00000000004000b0 <_start>
9       breakpoint     del  y   <PENDING>          main

حاولت الجري strace. هذا هو ناتجه:

execve("./helloworld", ["./helloworld"], [/* 39 vars */]) = 0
write(0, NULL, 12 <unfinished ... exit status 1>
  1. اشرح معلمات write(0, NULL, 12) استدعاء النظام في إخراج Strace؟
  2. ماذا بالضبط يحدث؟ اريد ان اعرف السبب بالضبط الخروج مع exitstatus = 1؟
  3. هل يمكن لشخص ما أن يريني كيفية تصحيح هذا البرنامج باستخدام GDB؟
  4. لماذا قاموا بتغيير أرقام مكالمات النظام؟
  5. يرجى تغيير هذا البرنامج بشكل مناسب حتى يمكن تشغيله بشكل صحيح على هذا الجهاز.

تعديل:

بعد قراءة إجابة بول آر. راجعت ملفاتي

claws@claws-desktop:~$ file ./hello.o 
./hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

claws@claws-desktop:~$ file ./hello
./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

وأنا أتفق معه على أن هذه يجب أن تكون قزم 32 بت قابلة للنقل وقابلة للتنفيذ. لكن هذا لا يجيب على أسئلتي. كل أسئلتي لا تزال أسئلة. ما الذي يحدث بالضبط في هذه الحالة؟ هل يمكن لأحد أن يجيب على أسئلتي وتقديم نسخة x86-64 من هذا الرمز؟

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

المحلول

تذكر أن كل شيء افتراضيًا على نظام التشغيل 64 بت يميل إلى تولي 64 بت. تحتاج إلى التأكد من أنك (أ) باستخدام الإصدارات 32 بت من #includes عند الاقتضاء (ب) الارتباط بمكتبات 32 بت و (ج) بناء 32 بت قابلة للتنفيذ. من المحتمل أن يساعدك ذلك إذا عرضت محتويات Makefile إذا كان لديك واحدة ، وإلا فإن الأوامر التي تستخدمها لبناء هذا المثال.

fwiw لقد غيرت رمزك قليلاً (_start -> Main):

#include <asm/unistd.h>
#include <syscall.h>
#define STDOUT 1

    .data
hellostr:
    .ascii "hello wolrd\n" ;
helloend:

    .text
    .globl main

main:
    movl $(SYS_write) , %eax  //ssize_t write(int fd, const void *buf, size_t count);
    movl $(STDOUT) , %ebx
    movl $hellostr , %ecx
    movl $(helloend-hellostr) , %edx
    int $0x80

    movl $(SYS_exit), %eax //void _exit(int status);
    xorl %ebx, %ebx
    int $0x80

    ret

وبناءها مثل هذا:

$ gcc -Wall test.S -m32 -o test

verfied أن لدينا 32 بت قابلة للتنفيذ:

$ file test
test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.4, dynamically linked (uses shared libs), not stripped

ويبدو أنه يعمل بشكل جيد:

$ ./test
hello wolrd

نصائح أخرى

كما لاحظ بول ، إذا كنت ترغب في إنشاء ثنائيات 32 بت على نظام 64 بت ، فأنت بحاجة إلى استخدام علامة -M32 ، والتي قد لا تكون متوفرة افتراضيًا على التثبيت (بعض توزيعات Linux 64 بت لا قم بتضمين دعم 32 بت/Linker/LIB بشكل افتراضي).

من ناحية أخرى ، يمكنك بدلاً من ذلك إنشاء الكود الخاص بك على أنه 64 بت ، وفي هذه الحالة تحتاج إلى استخدام اتفاقيات الاتصال 64 بت. في هذه الحالة ، يذهب رقم استدعاء النظام في ٪ rax ، وتذهب الوسائط في ٪ RDI و ٪ RSI و ٪ RDX

تعديل

أفضل مكان وجدته لهذا www.x86-64.org, ، على وجه التحديد ABI.PDF

يمكن لوحدة المعالجة المركزية 64 بت تشغيل رمز 32 بت ، ولكن يتعين عليهم استخدام وضع خاص للقيام بذلك. كل هذه الإرشادات صالحة في وضع 64 بت ، لذلك لا شيء يمنعك من بناء قابلة للتنفيذ 64 بت.

رمزك يبني ويعمل بشكل صحيح مع gcc -m32 -nostdlib hello.S. ذلك بسبب -m32 تعريف __i386, ، لذا /usr/include/asm/unistd.h يشمل <asm/unistd_32.h>, التي لديها الثوابت الصحيحة ل int $0x80 أبي.

أنظر أيضا تجميع ثنائيات 32 بت على نظام 64 بت (GNU أدوات) لمعرفة المزيد عن _start ضد. main مع/بدون libc و static مقابل Dynamic Executables.

$ file a.out 
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=973fd6a0b7fa15b2d95420c7a96e454641c31b24, not stripped

$ strace ./a.out  > /dev/null
execve("./a.out", ["./a.out"], 0x7ffd43582110 /* 64 vars */) = 0
strace: [ Process PID=2773 runs in 32 bit mode. ]
write(1, "hello wolrd\n", 12)           = 12
exit(0)                                 = ?
+++ exited with 0 +++

من الناحية الفنية ، إذا كنت قد استخدمت أرقام المكالمات المناسبة ، فسيحدث رمزك للعمل من وضع 64 بت أيضًا: ماذا يحدث إذا كنت تستخدم 32 بت 0x80 Linux ABI في رمز 64 بت؟ لكن int 0x80 لا ينصح به في رمز 64 بت. (في الواقع ، لا ينصح به أبدًا. من أجل الكفاءة ، يجب أن يتصل رمز 32 بت من خلال صفحة VDSO المصدرة من kernel حتى تتمكن من استخدامها sysenter لمكالمات النظام السريع على وحدات المعالجة المركزية التي تدعمها).


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

سؤال جيد.

على Linux ، int $0x80 مع eax=1 هو sys_exit(ebx), ، بغض النظر عن الوضع الذي كانت عملية الاتصال فيه. يتوفر ABI 32 بت في وضع 64 بت (ما لم يتم تجميع نواةك دون دعم I386 ABI) ، ولكن لا تستخدمه. حالة الخروج الخاصة بك من movl $(STDOUT), %ebx.

(راجع للشغل ، هناك أ STDOUT_FILENO الماكرو المحدد في unistd.h, ، لكن لا يمكنك ذلك #include <unistd.h> من .S لأنه يحتوي أيضًا على نماذج C التي ليست صالحة لـ ASM Syntax.)

لاحظ أن __NR_exit من عند unistd_32.h و __NR_write من عند unistd_64.h كلاهما 1, ، بحيث أول int $0x80 يخرج عمليتك. أنت تستخدم أرقام مكالمات النظام الخاطئة لـ ABI التي تستدعيها.


strace هو فك تشفيره بشكل غير صحيح, ، كما لو كنت قد استدعت syscall (لأن هذه هي عملية ABI A 64 بت من المتوقع استخدامها). ما هي اتفاقيات الاتصال لمكالمات نظام UNIX & Linux على x86-64

eax=1 / syscall يعني write(rd=edi, buf=rsi, len=rdx), ، وهذه هي الطريقة strace هو فك تشفير الخاص بك بشكل غير صحيح int $0x80.

rdi و rsi نكون 0 (الملقب ب NULL) عند الدخول إلى _start, ، ويقوم الرمز الخاص بك بتعيين rdx=12 مع movl $(helloend-hellostr) , %edx.

يقوم Linux بتهيئة السجلات إلى الصفر في عملية جديدة بعد execve. (يقول ABI غير محدد ، يختار Linux صفر لتجنب تسرب المعلومات). في الخاص بك المرتبطة بشكل ثابت ، تنفذ ، _start هو أول رمز فضاء المستخدم الذي يتم تشغيله. (في تنفيذ ديناميكي ، يعمل الرابط الديناميكي من قبل _start, ، ويترك القمامة في السجلات).

انظر أيضا علامة wiki لمزيد من روابط ASM.

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