在使用系统调用制作的 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
指令来执行此操作。这可以在函数序言之后立即完成。
在大学 - 汇编语言 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
指令来执行此操作。这可以在函数序言之后立即完成。