linux - প্রিন্টফের পরিবর্তে লিনাক্স সিস্টেম কল সহ এটিএন্ডটি সিনট্যাক্সের সাথে একটি স্ট্রিং হিসাবে একটি পূর্ণসংখ্যা মুদ্রণ




assembly x86 (2)

@ পেড g জি যেমন উল্লেখ করেছে, আপনি বেশ কয়েকটি কাজ ভুল করছেন: 64৪ বিট কোডে int 0x80 32-বিট এবিআই write() সিস্টেম কলটিতে পয়েন্টারের পরিবর্তে অক্ষর মানগুলি পাস করা।

এখানে কীভাবে 64৪-বিট লিনাক্সে কোনও পূর্ণসংখ্যা মুদ্রণ করা যায়, সহজ এবং কিছুটা দক্ষ উপায়। দেখুন জিসিসি কেন পূর্ণসংখ্যা বিভাগ প্রয়োগের ক্ষেত্রে অদ্ভুত সংখ্যার দ্বারা গুণ ব্যবহার করে? 10 দ্বারা বিভাজনের জন্য div r64 এড়ানোর জন্য, কারণ এটি খুব ধীর ( ইন্টেল স্কাইলেকে 21 থেকে 83 চক্র )। একটি গুণগত বিপরীতটি এই ফাংশনটিকে কেবল "কিছুটা" না করেই দক্ষ করে তুলবে। (তবে অবশ্যই এখনও অপ্টিমাইজেশনের জন্য জায়গা থাকবে ...)

সিস্টেম কলগুলি ব্যয়বহুল (সম্ভবত write(1, buf, 1) জন্য হাজার হাজার চক্র write(1, buf, 1) ), এবং রেজিস্টারগুলিতে লুপ write(1, buf, 1) অভ্যন্তরে একটি write(1, buf, 1) যাতে এটি অসুবিধে না হয় এবং ততই অদক্ষ। আমাদের অক্ষরগুলি একটি ছোট বাফারে মুদ্রণ ক্রমে write() সর্বনিম্ন ঠিকানায় সর্বাধিক উল্লেখযোগ্য সংখ্যা), এবং এটিতে একটি write() সিস্টেম কল করা উচিত।

তবে তারপরে আমাদের একটি বাফার দরকার। -৪-বিট পূর্ণসংখ্যার সর্বোচ্চ দৈর্ঘ্য কেবলমাত্র দশমিক দশমিক, সুতরাং আমরা কেবল কিছু স্ট্যাক স্পেস ব্যবহার করতে পারি। X86-64 লিনাক্সে, আমরা আরএসপি সংশোধন করে আরএসপি (128 বি অবধি) এর নীচে স্ট্যাক স্পেস ব্যবহার করতে পারি use একে red-zone বলা হয়।

হার্ড-কোডিং সিস্টেম-কল নম্বরগুলির পরিবর্তে, জিএএস ব্যবহার করে .h ফাইলে সংজ্ঞায়িত স্থায়ী ব্যবহারগুলি সহজ করে তোলে। ফাংশনের শেষের দিকে mov $__NR_write, %eax নোট করুন। X86-64 সিস্টেমভি এবিআই ফাংশন-কলিং কনভেনশনের অনুরূপ নিবন্ধগুলিতে সিস্টেম-কল আর্গুমেন্টগুলি পাস করে । (সুতরাং এটি 32-বিট int 0x80 থেকে সম্পূর্ণ আলাদা রেজিস্টার)

#include <asm/unistd_64.h>    // This is a standard glibc header file
// It contains no C code, only only #define constants, so we can include it from asm without syntax errors.

.p2align 4
.globl print_integer            #void print_uint64(uint64_t value)
print_uint64:
    lea   -1(%rsp), %rsi        # We use the 128B red-zone as a buffer to hold the string
                                # a 64-bit integer is at most 20 digits long in base 10, so it fits.

    movb  $'\n', (%rsi)         # store the trailing newline byte.  (Right below the return address).
    # If you need a null-terminated string, leave an extra byte of room and store '\n\0'.  Or  push $'\n'

    mov    $10, %ecx            # same as  mov $10, %rcx  but 2 bytes shorter
    # note that newline (\n) has ASCII code 10, so we could actually have used  movb %cl to save code size.

    mov    %rdi, %rax           # function arg arrives in RDI; we need it in RAX for div
.Ltoascii_digit:                # do{
    xor    %edx, %edx
    div    %rcx                 #  rax = rdx:rax / 10.  rdx = remainder

                                # store digits in MSD-first printing order, working backwards from the end of the string
    add    $'0', %edx           # integer to ASCII.  %dl would work, too, since we know this is 0-9
    dec    %rsi
    mov    %dl, (%rsi)          # *--p = (value%10) + '0';

    test   %rax, %rax
    jnz  .Ltoascii_digit        # } while(value != 0)
    # If we used a loop-counter to print a fixed number of digits, we would get leading zeros
    # The do{}while() loop structure means the loop runs at least once, so we get "0\n" for input=0

    # Then print the whole string with one system call
    mov   $__NR_write, %eax     # SYS_write, from unistd_64.h
    mov   $1, %edi              # fd=1
    # %rsi = start of the buffer
    mov   %rsp, %rdx
    sub   %rsi, %rdx            # length = one_past_end - start
    syscall                     # sys_write(fd=1 /*rdi*/, buf /*rsi*/, length /*rdx*/); 64-bit ABI
    # rax = return value (or -errno)
    # rcx and r11 = garbage (destroyed by syscall/sysret)
    # all other registers = unmodified (saved/restored by the kernel)

    # we don't need to restore any registers, and we didn't modify RSP.
    ret

এই ফাংশনটি পরীক্ষা করতে, আমি এটি কল করতে এবং প্রস্থান করতে একই ফাইলটিতে এটি রেখেছি:

.p2align 4
.globl _start
_start:
    mov    $10120123425329922, %rdi
#    mov    $0, %edi    # Yes, it does work with input = 0
    call   print_uint64

    xor    %edi, %edi
    mov    $__NR_exit, %eax
    syscall                             # sys_exit(0)

আমি এটিকে একটি স্ট্যাটিক বাইনারি (কোনও libc সহ) তৈরি করেছি:

$ gcc -Wall -nostdlib print-integer.S && ./a.out 
10120123425329922
$ strace ./a.out  > /dev/null
execve("./a.out", ["./a.out"], 0x7fffcb097340 /* 51 vars */) = 0
write(1, "10120123425329922\n", 18)     = 18
exit(0)                                 = ?
+++ exited with 0 +++
$ file ./a.out 
./a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=69b865d1e535d5b174004ce08736e78fade37d84, not stripped

সম্পর্কিত: লিনাক্স x86-32 প্রসারিত-নির্ভুলতা লুপ যা প্রতিটি 32-বিট "অঙ্গ" থেকে 9 দশমিক অঙ্ক প্রিন্ট করে: দেখুন টোটাসি_ডিজিট: আমার এক্সট্রিম ফিবোনাকির কোড-গল্ফ উত্তরটিতে । এটি কোড-আকারের জন্য (এমনকি গতির ব্যয়েও) অনুকূলিত হয়েছে, তবে ভাল মন্তব্য করেছে।

এটি আপনার মতো div ব্যবহার করে, কারণ এটি একটি দ্রুত গুণক বিপরীতমুখী ব্যবহারের চেয়ে ছোট)। এটি বাইরের loop জন্য loop ব্যবহার করে (প্রসারিত নির্ভুলতার জন্য একাধিক সংখ্যার উপরে), আবার গতির ব্যয়ে কোড-আকারের জন্য।

এটি 32-বিট int 0x80 এবিআই ব্যবহার করে এবং একটি বাফারে মুদ্রণ করে যা "পুরাতন" ফিবোনাচি মান ধরেছিল, বর্তমান নয়।

দক্ষ asm পাওয়ার আরেকটি উপায় হ'ল সি সংকলক। কেবলমাত্র ওভার অঙ্কের লুপের জন্য, এই সি উত্সটির জন্য জিসিসি বা ঝাঁকুনি কী উত্পন্ন হয় তা দেখুন (যা মূলত এএসএমটি কী করছে)। গডবোল্ট সংকলক এক্সপ্লোরার বিভিন্ন বিকল্প এবং বিভিন্ন সংকলক সংস্করণ দিয়ে চেষ্টা করা সহজ করে তোলে।

Gcc7.2 -O3 asm আউটপুট দেখুন যা মুদ্রণ_উইন্ট 64 এ লুপের জন্য প্রায় ড্রপ-ইন প্রতিস্থাপন (কারণ আমি একই রেজিস্টারগুলিতে যেতে print_uint64 বেছে নিয়েছি):

void itoa_end(unsigned long val, char *p_end) {
  const unsigned base = 10;
  do {
    *--p_end = (val % base) + '0';
    val /= base;
  } while(val);

  // write(1, p_end, orig-current);
}

আমি স্কাইলেক আই 7-6700k এ সিস্কল নির্দেশটি মন্তব্য করে এবং ফাংশন কলটির চারপাশে পুনরাবৃত্তি লুপটি রেখে পারফরম্যান্স পরীক্ষা করেছি। div %rcx স্ট্রিং ( 10120123425329922 ) বাফারে সংরক্ষণের জন্য div %rcx সহ সংস্করণ তুলনায় mul %rcx / mul %rcx shr $3, %rdx সহ সংস্করণটি প্রায় 5 গুণ বেশি div %rcx । ডিভিশন সংস্করণটি প্রতি ঘড়ি প্রতি 0.25 নির্দেশে চলেছে, যখন মুল সংস্করণটি প্রতি ঘড়ি প্রতি 2.65 নির্দেশে চলেছে (যদিও আরও অনেক নির্দেশাবলীর প্রয়োজন রয়েছে)।

এটি 2 দ্বারা তালিকাভুক্ত হওয়া, এবং 100 দ্বারা বিভাজন করা এবং এর বাকী অংশটি 2 অঙ্কে বিভক্ত করার পক্ষে মূল্যবান হতে পারে। mul + shr লেটেন্সিতে সহজ সংস্করণে বাধা থাকলে, এটি আরও অনেক বেশি ভাল দিকনির্দেশ-স্তরের সমান্তরালতা দেয়। শূন্যের সাথে val এনে দেবে এমন বহুগুণ / শিফট অপারেশনের চেইনটি অর্ধেক দীর্ঘ হবে, প্রতিটি সংক্ষিপ্ত স্বতন্ত্রতা নির্ভরশীল শৃঙ্খলে 0-99 এর বাকি অংশগুলি পরিচালনা করতে আরও কাজ করা হবে।

এটি অ্যান্ড টি সিনট্যাক্স অনুসরণ করে একটি সংখ্যার ফ্যাক্টরিয়াল প্রদর্শনের জন্য আমি একটি অ্যাসেম্বলি প্রোগ্রাম লিখেছি ut তবে এটি কাজ করছে না my আমার কোডটি এখানে রয়েছে

.text 

.globl _start

_start:
movq $5,%rcx
movq $5,%rax


Repeat:                     #function to calculate factorial
   decq %rcx
   cmp $0,%rcx
   je print
   imul %rcx,%rax
   cmp $1,%rcx
   jne Repeat
# Now result of factorial stored in rax
print:
     xorq %rsi, %rsi

  # function to print integer result digit by digit by pushing in 
       #stack
  loop:
    movq $0, %rdx
    movq $10, %rbx
    divq %rbx
    addq $48, %rdx
    pushq %rdx
    incq %rsi
    cmpq $0, %rax
    jz   next
    jmp loop

  next:
    cmpq $0, %rsi
    jz   bye
    popq %rcx
    decq %rsi
    movq $4, %rax
    movq $1, %rbx
    movq $1, %rdx
    int  $0x80
    addq $4, %rsp
    jmp  next
bye:
movq $1,%rax
movq $0, %rbx
int  $0x80


.data
   num : .byte 5

এই প্রোগ্রামটি কোনও কিছুই মুদ্রণ করছে না, আমি লুপ ফাংশন পর্যন্ত এটি সূক্ষ্মভাবে কাজ করতে ভিজ্যুয়ালাইজ করতে জিডিবিও ব্যবহার করেছি তবে পরবর্তী সময়ে কিছু এলোমেলো মান বিভিন্ন রেজিস্টারে প্রবেশ করা শুরু করে deb আমাকে ডিবাগ করতে সহায়তা করুন যাতে এটি ফ্যাকটোরিয়াল মুদ্রণ করতে পারে।


বেশ কিছু বিষয়:

0) আমি অনুমান করি এটি b৪ বি লিনাক্স পরিবেশ, তবে আপনার এটি বলা উচিত ছিল (যদি এটি না হয় তবে আমার কিছু বিষয় অবৈধ হবে)

1) int 0x80 বি কল, তবে আপনি 64 বি রেজিস্টার ব্যবহার করছেন, সুতরাং আপনার syscall (এবং বিভিন্ন যুক্তি) ব্যবহার করা উচিত

2) ecx int 0x80, eax=4 ecx int 0x80, eax=4 জন্য ecx মেমরির ঠিকানা থাকতে হবে, যেখানে সামগ্রী সংরক্ষণ করা হয়েছে, যখন আপনি এএসসিআইআই অক্ষরটি ecx = অবৈধ মেমরি অ্যাক্সেসে দেন (প্রথম কলটিতে ত্রুটি ফিরে পাওয়া উচিত, অর্থাৎ eax নেতিবাচক মান) । অথবা strace <your binary> ব্যবহার করে ভুল যুক্তি প্রকাশিত হওয়া উচিত + ত্রুটি ফিরে এসেছে।

3) কেন addq $4, %rsp ? আমাকে বোঝায় না, আপনি rsp ক্ষতিগ্রস্থ rsp , সুতরাং পরবর্তী pop rcx ভুল মানটি পপ করবে এবং শেষ পর্যন্ত আপনি স্ট্যাকের দিকে "আপ" চালিয়ে যাবেন।

... সম্ভবত আরও কিছু, আমি এটি ডিবাগ করিনি, এই তালিকাটি কেবল উত্সটি পড়ে (তাই আমি কোনও কিছু সম্পর্কে ভুলও হতে পারি, যদিও এটি বিরল হবে)।

আপনার কোডটি বিটিডব্লিউ কাজ করছে । এটি কেবল আপনি যা প্রত্যাশা করেছিলেন তা করে না। তবে সিপিইউ নকশা করা হয়েছে এবং আপনি কোডটিতে যা লিখেছেন তা হুবহু কাজ করুন। এটি আপনি যা চেয়েছিলেন তা অর্জন করে, বা বোঝায় যে, এটি ভিন্ন বিষয়, তবে এইচডাব্লু বা এসেম্বলারের দোষ দেবেন না।

... আমি কীভাবে রুটিন ঠিক করা যেতে পারে তাড়াতাড়ি অনুমান করতে পারি (কেবলমাত্র আংশিক হ্যাক-ফিক্স, এখনও syscall বি লিনাক্সের অধীনে সিস্কলের জন্য পুনর্লিখনের প্রয়োজন আছে):

  next:
    cmpq $0, %rsi
    jz   bye
    movq %rsp,%rcx    ; make ecx to point to stack memory (with stored char)
      ; this will work if you are lucky enough that rsp fits into 32b
      ; if it is beyond 4GiB logical address, then you have bad luck (syscall needed)
    decq %rsi
    movq $4, %rax
    movq $1, %rbx
    movq $1, %rdx
    int  $0x80
    addq $8, %rsp     ; now rsp += 8; is needed, because there's no POP
    jmp  next

আবার নিজেকে চেষ্টা করে দেখিনি, কেবল এটি মাথা থেকে লিখছি, সুতরাং কীভাবে এটি পরিস্থিতির পরিবর্তন হয়েছে তা আমাকে জানান।





att