动态链接 GAS 程序集文件时出现问题 (x64)

Issues when linking GAS assembly files dynamically (x64)

我正在尝试 link 2 个文件。我正在尝试使用 3 个符号(汇编程序)link。它与静态 linking 完美配合,但当尝试动态执行时 - 我收到错误。

/usr/bin/ld: warning: type and size of dynamic symbol `parse_intro' are not defined                                                                                                                         
/usr/bin/ld: warning: type and size of dynamic symbol `time_to_print' are not defined                                                                                                                       
/usr/bin/ld: warning: type and size of dynamic symbol `optimizing' are not defined  

首先,我将一个文件制作到一个共享库中,我想从中导出 3 个符号。此文件的代码:

    .data
premsg:
    .ascii "0x"                                     #to make the number have distinct x16 look when printed, this will be used as a prefix before a number

msg:                                                #placeholder msg label, reserved memory will be used to store parsed number
    .space 16                                       #16 bytes for 32 ascii numbers

nextline:
    .ascii "\n"                                     #switching to the next line after 16 ascii numbers

var1:
    .quad 0x00000000000ef12b                        #number which will be parsed

    .global parse_intro
    .global optimizing
    .global time_to_print

    .text

parse_intro:
                                                    #binary mask for cutting smallest 4 bits (single number) from the 8 byte number
    mov [=11=]b0000000000000000000000000000000000000000000000000000000000001111, %r9
    mov [=11=]x1, %rbx                                  #multiplier that will be used to shift from first 4 bits to proper position in the ascii number
    mov [=11=]x0, %r12                                  #register that will hold first half of the reversed number
    mov [=11=]x0, %r14                                  #register that will hold second half of the reversed number
    jmp parse_start

parse_start:                                        #main body of the parsing number to ASCII procedure
    mov %r8, %rax                                   #making a copy of a number before shifting it
    shr , %r8                                     #shifting original number to 4 numbers right to go to the next number on the next step
    and %r9, %rax                                   #taking last 4 bits from a number
    cmp [=11=]xa, %rax                                  #checking if a number is smaller than 0xa
    jl zero_to_9                                    #jumping to do +30 procedure if it's not x16 numeric symbol

    add [=11=]x57, %rax                                 #adding 57 because to change a number to ASCII number from a to f you need to add x16 57
    mul %rbx
    mul %rbx                                        #we multiplying it twice because we go from 8 bytes to 16 bytes
    add %rax, %r12                                  #adding summed with 30 and increased twice by rbx coefficient piece of the initial number to the previous pieces of a number to put it together in ASCII
    shl , %rbx                                    #shifting rbx 4 bits to the left to increase it by mul 10 without rax shenanigans
    jmp check_for_overflow                          #going to overflow check after adding 8 bytes to %r12


zero_to_9:                                          #function to change from bit number to ascii number
    add [=11=]x30, %rax                                 #adding 30 because to change a number to ASCII number from 0 to 9 you need to add x16 30
    mul %rbx
    mul %rbx                                        #we multiplying it twice because we go from 8 bytes to 16 bytes
    add %rax, %r12                                  #adding summed with 30 and increased twice by rbx coefficient piece of the initial number to the previous pieces of a number to put it together in ASCII
    shl , %rbx                                    #shifting rbx 4 bits to the left to increase it by mul 10 without rax shenanigans
    jmp check_for_overflow                          #going to overflow check after adding 8 bytes to %r12

check_for_overflow:                                 #function to store half of the value inside a second register, because it goes from 8 bytes to 16 bytes when parsed to ASCII
    mov [=11=]x1000000000000000, %rax                   #value for overflow check
    cmp %rax, %r12                                  #checking if r12 is overflowed
    jle looping                                     #doing a loop to parse_start with retq if loop ends in case if %r12 is not going to get overflow
    cmp [=11=]x1, %rcx                                  #checking if count is 1 and then
    je finishing                                    #Finishing to avoid moving %r12 to %r14 another time
    mov %r12, %r14                                  #moving %r12 value to store in %r14
    xor %r12, %r12                                  #resetting %r12
    mov [=11=]x1, %rbx                                  #resetting rbx to start from the first position
    jmp looping                                     #doing a loop to parse_start with retq if loop ends


looping:
    loop parse_start
    retq                                            #exiting back to start if loops end, otherwise back to parse_start and decrementing %rcx

optimizing:
                                                    #binary mask for cutting lower 4 bytes out in optimization routine
    mov [=11=]b1111111111111111111111111111111100000000000000000000000000000000, %rbx

    mov , %rcx                                   #the counter
    movq %r8, %rax                                  #moving the initial number value to accumulator register %rax
    andq %rbx, %rax                                 #leaving only first 32 not as 0 to check if biggest part of the number is full of 0 or not
    cmp [=11=], %rax                                    #checking if number is full of 0
    jne finishing                                   #first (from the left) 32 bits aren't full of 0 so we can't ignore them
    subq , %rcx                                   #second half of a number is full of 0, so we can only count from 8 instead of 16
    retq                                            #returning to _start

finishing:
    retq                                            #little function to jump return

time_to_print:
    mov [=11=]b1111111100000000000000000000000000000000000000000000000000000000, %r9
    cmp [=11=], %r14                                    #checking if we skipped 4 bytes because the original had zeroes in the biggest part of the number
    je skipped_bytes                                #jumping to function that will swap r14 and r12, so that lower part is kept in r14 and r12 is full of zeroes (0x30 in ASCII)
    mov , %rcx                                   #making a counter which would stop the jump loop which would also work as a shift left value
    call byte_fun                                   #calling the reversing procedure
    mov %r14, %r12                                  #moving second half of the number to r12
    mov %rax, %r14                                  #storing the first half of the number from the accumulator having the value after completing byte_fun to %r14
    mov , %rcx                                   #we are putting 56 and not 64 because the last step will be made after the loop to avoid additional actions
    call byte_fun                                   #reversing the second half of a number
    mov %rax, %r12                                  #moving stored second half of the number to the different register
    lea msg(%rip), %rax                             #storing the address of the msg inside %rax
    mov %r14, (%rax)                                #Putting on the first half of the reserved memory by msg, linked to %rax through previous instruction, the first half of the number needed to display formatted to ASCII saying mov value (rax) makes you move the value to the address stored in the register
    add , %rax                                    #adding 8 to address the second half of the reserved memory in msg
    mov %r12, (%rax)                                #putting the second half of the reversed number to an address of the last 8 bytes resrved by msg
    mov , %rax                                    #putting 1 to %rax for printing syscall
    mov , %rdi                                    #puttin 1 to %rdi for printing syscall
    lea msg(%rip), %rsi                             #taking a position independent link to the msg label with numbers related to ascii data and putting it to a printing register %rsi
    mov , %rdx                                   #setting length of 16 bits (2 per 1 number) and 1 bit for newline \n char
    syscall                                         #syscalling the print with the proper number
    retq                                            #returning to the _start body

byte_fun:                                           #start of the reverse function to save the entry point
    pop %rbp                                        #putting entry point to the %rbp register to successfully return after a few jumping back and forth
    jmp byte_reverse                                #jumping to the main body of the reverse function

byte_reverse:                                       #function which separates the highest 2 bits from the reversed number and then shifts the reversed number by 8 bits (2 numbers) to the left and then switches 8 bits to their proper position and pushes them to a stack.
    mov %r12, %rax                                  #putting a copy of the currently shifted reversed number to an accumulator register %rax
    shl , %r12                                    #shifting reversed number to the left for the next loop cycle
    and %r9, %rax                                   #applying binary mask which will only leave 8 bytes (or 2 numbers)
    shr %cl, %rax                                   #shifting current 8 bits of a reversed number right, prior to %rcx count, to reverse their position
    push %rax                                       #pushing a shifted piece of a number to a stack
    sub , %rcx                                    #substracting 8 from count to represent a shifting of the next 8 bits
    jne byte_reverse                                #if sub from %rcx not resulted in zero - we are looping
    push %r12                                       #pushing last 2 numbers of the reversed number, shifted to the left, as the final piece of the reversed value, which will be accessed first from stack to start the reverse
    mov , %rcx                                    #switching count register to 8 for a future loop inside byte_back
    xor %rax, %rax                                  #cleaning up the accumulator to 0
    jmp byte_back                                   #jumping to a code which put 8 elements inside stack all together and puts them back to stack as a single entity

byte_back:                                          #function which sums elements in the stack to get the reversed version of the number
    pop %rbx                                        #poppint highest stack element to a register
    add %rbx, %rax                                  #adding the highest element to an accumulator
    loop byte_back                                  #decrementing the %rcx counter and starting at byte back again
    push %rbp                                       #we exited the loop and now putting the address of the print procedure to the top of the stack to get back to a procedure after calling the byte_fun
    retq                                            #returning to print procedure

skipped_bytes:                                      #function to avoid issues with printing when only 4 bytes of the original number were evaluated in parsing
    #I put this print of "0x" to avoid _start code because I want to use this program as a library
    mov , %rax                                    #putting 1 to %rax for print syscall
    mov , %rdi                                    #putting 1 to %rdi for print syscall
    lea premsg(%rip), %rsi                          #putting a link to "0x" ascii value to %rsi to print it
    mov , %rdx                                    #setting 2 bytes to display 2 characters
    syscall                                         #syscall for printing "0x"
    mov %r12, %r14                                  #moving the first half of the reversed number to a register that will be pushed to a stack first, so it would be last when we get it back
    mov [=11=]x3030303030303030, %r12                   #changing a second half or reversed number to ascii zeroes
    jmp time_to_print                               #we are ready to start the printing procedure

我通过命令将这个程序制作成.so文件:

gcc printing.s -shared -o libprint.so

然后,我在主文件中使用此 .so 中的这 3 个过程(parse_intro、time_to_print 和优化),其中包含此代码

    .data
linked_space:                                                                           #space reserved for linked list nodes
    .space 0x3000

list_head:                                                                              #default list_head value
    .quad 0x0
    .quad 0x0

opening_bracket:                                                                        #part of printing function construct
    .ascii "["

straight_line:                                                                          #part of printing function construct
    .ascii "|"

closing_part:                                                                           #part of printing function construct
    .ascii "] -> "

last_part:                                                                              #part of printing function construct, representing empty "first" node
    .ascii "[empty|node]\n"

cut_error_text:
    .ascii "Error: can't cut a core node\n"


    .global _start

    .text





add_head:                                                                               #function which adds new element as head and makes a link to a previous head element
    mov list_head(%rip), %rax                                                           #moving contents of a label (link to a head node) to a register
    add , %rax                                                                       #Moving the link to a point where a new node will start (1 node is 16 bytes)
    mov %rbx, (%rax)                                                                    #Putting a value that we want to hold in a new node inside the value address of a new head node
    add , %rax                                                                        #Moving address inside register by 8 to put a link to a previous head
    mov list_head(%rip), %rdx                                                           #Moving old head address to an %rdx to put it then inside the new head
    mov %rdx, (%rax)                                                                    #Putting link of the old head inside a node of a new head
    add , list_head(%rip)                                                            #Changing label which points to the head element to the new node we created
    retq

cut_head:                                                                               #Function which cuts the head by shifting the label 16 bytes back, with exception check to avoid touching core node
    mov list_head(%rip), %rax                                                           #Putting link of the current head element which we will cutaway
    add , %rax                                                                        #Getting a link to a previous element to check if it's 0x0, which means it's a core node
    cmp [=13=], (%rax)                                                                      #Comparing it to 0
    je cut_error                                                                        #Jumping to error version of the cut if it's equal
    sub , %rax                                                                       #Moving to the beginning of the previous element, 16 bytes + 8 after previous add 8
    mov %rax, list_head(%rip)                                                           #Moving new link to a head label
    retq

cut_error:                                                                              #Function which prints error text and avoid cutting the core element
    mov , %rax                                                                        #Printing error text
    mov , %rdi
    lea cut_error_text(%rip), %rsi
    mov , %rdx
    syscall
    retq

pre_print:                                                                              #Intro for print to put first head element, to avoid issues with loop shenanigans
    push list_head(%rip)
    jmp print_node                                                                      #Moving to main print function

print_node:                                                                             #Function which takes functions from printing code and prints linked list visually
    pop %rbx                                                                            #Putting stored link to node value into %rbx
    push %rbx                                                                           #Pushing back to a stack a link to avoid mutations of a link after working with %rbx
    add , %rbx                                                                        #Moving link to the address part of the node with the address to a previous list
    mov (%rbx), %rax                                                                    #Putting actual address to the previous node to check if it's 0x0, which means, it's a first empty node
    cmp [=13=], %rax                                                                        #Comparing link inside %rax to 0
    je return_printing                                                                  #If it's zero - moving to the final part of the print, where the first empty node is printed with \n char
    mov , %rax                                                                        #Printing opening bracket
    mov , %rdi
    lea opening_bracket(%rip), %rsi
    mov , %rdx
    syscall
    pop %rbx                                                                            #Getting link to the value field of the current node
    mov (%rbx), %r8                                                                     #Moving it to %r8, which is a register that will contain a numeric value for future printing
    push %rbx                                                                           #Saving link stored inside %rbx because %rbx will be used in printing functions
    call optimizing                                                                     #Printing routine consists of 3 functions which need to be called from printing part of the program
    call parse_intro
    call time_to_print
    mov , %rax                                                                        #Printing straight line to separate value from link
    mov , %rdi
    lea straight_line(%rip), %rsi
    mov , %rdx
    syscall
    pop %rbx                                                                            #Putting link back from the stack again in rbx
    push %rbx                                                                           #Storing the link before mutating it again
    add , %rbx                                                                        #Changing link to the address which has a link to the previous element
    mov (%rbx), %r8                                                                     #Moving link to %r8 to print it
    call optimizing                                                                     #Launching a print routine
    call parse_intro
    call time_to_print
    mov , %rax                                                                        #Printing closing part of the node "construct"
    mov , %rdi
    lea closing_part(%rip), %rsi
    mov , %rdx
    syscall
    pop %rbx                                                                            #Popping link of the printed node to shift it 16 bytes back to move to the previous node
    sub , %rbx                                                                       #Moving to the previous node
    push %rbx                                                                           #Putting a link to the previous node inside stack to use it later
    jmp print_node                                                                      #Looping back to print_node

return_printing:                                                                        #Finalizing function which will print the first empty node and \n char
    mov , %rax
    mov , %rdi
    lea last_part(%rip), %rsi
    mov , %rdx
    syscall
    pop %rbx                                                                            #Since we didn't pop the stored value in loop body, we need to get rid of it from stack to jump back to _start
    retq

list_initialization:
    lea linked_space(%rip), %rax                                                        #initializing the first node, it already has 0x0 as value and it's needed to put on the space for the linked list
    mov %rax, list_head(%rip)                                                           #Putting link to the list_head, which currently contains the first node value/link 0x0, on the linked_space
    retq

_start:
    call list_initialization


    #call pre_print                                                                      #Function which prints the linked list
    #mov , %rbx                                                                        #%rbx will hold value which will be put inside new linked list node
    #call add_head                                                                       #Function which adds new node as head element
    #call pre_print
    #mov , %rbx
    #call add_head
    #call pre_print
    #mov , %rbx
    #call add_head
    #call pre_print
    #call cut_head                                                                       #Function which cuts head element and moves link to a previous element
    #call pre_print
    #call cut_head
    #call pre_print
    #call cut_head
    #call pre_print
    #call cut_head
    #call pre_print

    mov , %rax                                                                       #Exiting from a program
    xor %rdi, %rdi
    syscall

并编译成二进制文件,应该可以运行,通过命令:

gcc -L /home/*path_to_folder_with_so_file* -g -nostdlib -o output linkedlist.s -lprint

我也尝试编译同一个文件,但注释掉了程序试图从动态共享库访问文件的 6 行。这是此文件的“readelf”内容。

https://pastebin.com/WUQz09K2

并读取 .so 文件:

https://pastebin.com/1F74euqP

我可能遗漏了什么,为什么原始文件找不到从 .so 导入的符号?

由于我无法将评论作为解决方案,因此,我将自己提供答案,该答案由fuz提供。

这里要做的重要事情是注意错误消息(我知道,多么明显)。 GAS 程序集中的每个动态链接符号,即使没有 C 库,也需要在 .so 文件中分配类型和大小,因为 GCC 无法自行获取有关导出符号的信息,程序员需要明确地给它.例如,对于函数 foo

foo:
   ...
   ret

你需要通过输入

来给函数一个类型
.type foo, @function

在代码的某处,我在

之后的下一行做了
.global foo

行,我将相同的函数设置为全局函数,允许将其导出。

并且,要为动态符号指定大小,您需要将

.size foo, .-foo

在最后一条指令之后(例如,在“jmp”或“ret”之后)。 “点”是当前地址,foo 是“foo”函数中第一条指令的地址。因此,通过从当前地址中减去“foo”地址,您将获得“foo”的大小。