在使用系统调用制作的 printf 上输出两个字符串时遇到问题

Having trouble outputting both strings on my printf made with syscalls

在大学 - 汇编语言 C 课程 - 作业中,我需要仅使用系统调用来创建基本的 printf 函数。每当出现“%”时,我都需要检查下一个字符以确定如何实现字符或字符串。

如果有'c',用字符替换,如果有's',用字符串替换。如果还有另一个 '%',则输出它。教授说他知道这是一项艰巨的任务,所以如果解决方案部分实施就可以了,但我离完整的解决方案太近了,所以我想继续推进。

我对这段代码做了很多工作,并且对几乎每一行都做了评论,所以我想强调这是为了我的学习目的。 我可以使用 mov eax, [abp + 12] 打印出一个字符串 'woot woot' 或者使用 mov eax, [ebp + 16] 打印出第二个 'woot woot',但是我找不到同时打印两者的解决方案。这是我的难题。

感谢您的宝贵时间,祝您编码愉快!

这里有一份 link 的作业以供澄清:https://imgur.com/h9tP89j

这是我的示例输出:

Hello world
str3 is 'woot woots', isn't that cool?
A is a char, but so is %,, s again!

这是我的代码:

  4 segment .data
  5
  6     str1    db  "Hello world", 10, 0
  7     str2    db  "str3 is '%s', isn't that cool?", 10, 0
  8     str3    db  "woot woot", 0
  9     str4    db  "%c is a char, but so is %%, %s again!", 10, 0
 10
 11 segment .bss
 12
 13
 14 segment .text
 15     global  asm_main
 16
 17 asm_main:
 18     push    ebp
 19     mov     ebp, esp
 20     ; ********** CODE STARTS HERE **********
 21
 22     ;; EVERYTHING UP UNTIL THE PRINTF FUNCTION DOES NOT CHANGE AT ALL
 23
 24     ;   eax (syscall-number) What do we want done?  3 is Read, 4 is Write
 25     ;   ebx (other-info) Usually when do you want the thing done? Or printed?
 26     ;   0 is if you want to type something yourself, 1 is if you want to print something
 27     ;   ecx (other-info) Usually this is where you would put the string to be printed (example: str1)
 28     ;   edx (other-info) How long is the data that needs to be printed? You can ignore the null character
 29     ;   int 0x80 = Turn on the kernel and do the thing
 30
 31     push str1       ; push string 1 - 4 bytes
 32     call printf     ; call function
 33     add esp, 4      ; str1 is a dword with 4 bytes
 34
 35     push str3       ; push string 3 - 4 bytes
 36     push str2       ; push string 2 - 4 bytes
 37     call printf     ; call function
 38     add esp, 8      ; str3 and str2 is 8 bytes total
 39
 40     push str3       ; push string 3 - 4 bytes
 41     push 'A'        ; Push A character - it's still a dword so 4 bytes
 42     push str4       ; push string 4 - 4 bytes
 43     call printf     ; call function
 44     add esp, 8      ; two arguments, 8 bytes total
 45
 46     ; *********** CODE ENDS HERE ***********
 47     mov     eax, 0
 48     mov     esp, ebp
 49     pop     ebp
 50     ret
 51
 52 printf:
 53     push    ebp             ; Prologue - every function starts with this
 54     mov     ebp, esp        ; Prologue - and this
 55
 56     mov     edx, -1         ; this is a counter to walk through each string slowly
 57     mov     edi, -1
 58     loop:
 59     inc     edx     ; increment counter for each loop
 60     mov     esi, edx        ; constantly update this reserve to preserve counter, for use with offsetedx
 61     mov     eax, DWORD [ebp + 8]        ; set eax to the dword  pointer at ebp + 8
 62     cmp     BYTE [eax + edx], 0     ; compare the byte in the string with a null terminator
 63     je      loopEnd     ; if there is a null terminator, jump to the end of the loop
 64
 65         percentCheck:       ; each time we come up to a %, we want to check the next character to see how to proceed
 66         cmp     BYTE [eax + edx], 37    ; compare the current byte with a 37, which is is a '%' on the ascii table
 67         jne continue        ; if there is no percentage, we can continue walking through the string
 68         inc     edx         ; move to the next byte
 69
 70             charCheck:
 71             cmp     BYTE [eax + edx], 99    ; compare the byte with a 99, which is 'c' on the ascii table
 72             jne     stringCheck     ; if there is no 'c', move to the next check
 73             mov     eax, 4      ; syscall write operation
 74             mov     ebx, 1      ; syscall for printing to screen
 75             lea     ecx, [ebp + 12]     ; pointer is possibly on the character. If not...?
 76
 77                     offsetCheck:        ; my idea is to check for the byte where ecx is pointing to see if there's an 'A'
 78                     je      offsetEnd       ; if it is, then output that bad boy!
 79                     add     ebp, 4      ; if not, then add to the stack to adjust for the offset
 80                     lea     ecx, [ebp]  ; now point ecx to the new pointer on the stack 
 81                     jmp     offsetCheck     ; run it again to make sure you are poiting to the 'A' character
 82                     offsetEnd:
 83
 84             int     0x80        ; make the kernel do the thing
 85             jmp     loop        ; re-run the loop
 86
 87             stringCheck:        ; this loop is a little tricky, as we need to be able to point to the correct string to output instead of the 's', but w$
 88             cmp     BYTE [eax + edx], 115       ; compare the byte with a 115, which is an 's' on the ascii table
 89             jne     continue        ;  if there is no 's', just let the string keep going
 90             mov     edx, -1     ; to calculate string length, just use the walktrhough loop again
 91                 offsetedx:
 92                 inc     edx     ; edx is our counter here
 93 ;               mov     edi, edx
 94                 mov     eax, DWORD [ebp + 8]        ; set eax to the dword pointer at ebp + 8 again
 95                 cmp     BYTE [eax + edx], 0     ; checking for a null terminator
 96                 je      offsetedxEnd        ; if there is a null terminator, assume we have reached the end of the string we wanted to drop in, and proc$
 97
 98                 mov     eax, 4      ; syscall write operation
 99                 mov     ebx, 1      ; syscall for printing to screen
100                 mov     ecx, DWORD [ebp + 12]       ; having trouble figuring out how to dymically set this to the right place. What to compare ecx to? $
101                 cmp     edi, -1
102                 je      continueoffset
103                     inc     edi     ; trying to increment edi so on the next check, I can set ecx to run the second 'woot woot' output
104                     mov     ecx, DWORD [ebp + 4]        ; this will output the sencond woot woot, but I can't get it to make the adjustment
105
106                 continueoffset:
107                 mov     edx, 9
108 ;               mov     edi, ecx
109                 int     0x80
110 ;;              inc     edi
111 ;               mov     edx, edi
112 ;               jmp     offsetedx
113                 offsetedxEnd:
114
115 ;           int     0x80            ; let the kernel do its thing
116             mov     edx, esi        ; make sure to put edx back to what it was supposed to be so the top loop isn't screwed up
117             jmp     loop            ; re-run the loop
118
119     continue:
120     mov     eax, 4      ; SYS_write - Print the thing out
121     mov     ebx, 1      ; STDOUT (terminal) - Write to screen
122     mov     ecx, DWORD [ebp + 8]    ; sets pointer to format string
123
124     add     ecx, edx        ; added counter to pointer, which ecx is pointing to
125     mov     edx, 1          ;; Want edx to only output 1 character, but after the output, we need it restored to original count
126
127     int     0x80            ; kernel please grant me your strength
128     mov     edx, esi        ; Extra important since we need edx to be walking through the string, so it needs to be restored to where it was
129     jmp      loop           ; run that loop back again
130
131     loopEnd:
132
133     mov     esp, ebp        ; Epilogue - every function ends with this
134     pop     ebp             ; Epilogue - and this
135     ret                     ; Epilogue - also this

使用stack frames时,不要在prologue/epilogue.

函数外修改EBP寄存器

由于您的所有寄存器都被使用,您需要堆栈上的局部变量始终指向下一个未使用的 vararg 参数的地址。这个变量应该初始化为ebp+12,因为那是printf的第二个参数的地址,也就是第一个vararg参数。使用该参数后,您应该将该局部变量递增 4,使其指向下一个可变参数。

只要此变量始终指向下一个未使用的可变参数,您应该可以轻松找到下一个参数。

为了给这样的局部变量分配space,需要在栈上分配4个字节。您可以使用 push 指令或 sub esp, 4 指令来执行此操作。这可以在函数序言之后立即完成。