I was going through one of my class's textbooks and I stumbled upon this problem:
Write a function in ARM assembly language which will insert a string into another string at a specific location. The function is:
char * csinsert( char * s1, char * s2, int loc ) ;
The function has a pointer to s1 in a1, a pointer to s2 in a2, and an integer in a3 as to where the insertion takes place. The function returns a pointer in a1 to the new string.
You can use the library functions strlen and malloc.
strlen has as input the pointer to the string in a1 and returns the length in a1.
malloc will allocate space for the new string where a1 on input is the size in bytes of the space requested and on output a1 is a pointer to the requested space.
Remember the registers a1-a4 do not retain their values across function calls.
This is the C language driver for the string insert I created:
#include <stdio.h>
extern char * csinsert( char * s1, char * s2, int loc ) ;
int main( int argc, char * argv[] )
{
char * s1 = "String 1 are combined" ;
char * s2 = " and string 2 " ;
int loc = 8 ;
char * result ;
result = csinsert( s1, s2, loc ) ;
printf( "Result: %s\n", result ) ;
}
My assembly language code so far is:
.global csinsert
csinsert:
stmfd sp!, {v1-v6, lr}
mov v1, a1
bl strlen
add a1, a1, #1
mov v2, a1
add a2, a2
mov v3, a2
add a3, a3
bl malloc
mov v3, #0
loop:
ldrb v4, [v1], #1
subs v2, v2, #1
add v4, v4, a2
strb v4, [a1], #1
bne loop
ldmfd sp!, {v1-v6, pc} @std
.end
I don't think my code works properly. When I link the two finals, there is no result given back. Why does my code not insert the string properly? I believe the issue is in the assembly program, is it not returning anything?
Can anyone explain what my mistake is? I'm not sure how to use the library functions the question hints to.
Thanks!
Caveat: I've been doing asm for 40+, I've looked at arm a bit, but not used it. However, I pulled the arm ABI document.
As the problem stated, a1-a4 are not preserved across a call, which matches the ABI. You saved your a1, but you did not save your a2 or a3.
strlen
[or any other function] is permitted to use a1-a4 as scratch regs. So, for efficiency, my guess is that strlen
[or malloc
] is using a2-a4 as scratch and [from your perspective] corrupting some of the register values.
By the time you get to loop:
, a2 is probably a bogus journey :-)
UPDATE
I started to clean up your asm. Style is 10x more important in asm than C. Every asm line should have a sidebar comment. And add a blank line here or there. Because you didn't post your updated code, I had to guess at the changes and after a bit, I realized you only had about 25% or so. Plus, I started to mess things up.
I split the problem into three parts:
- Code in C
- Take C code and generate arm pseudo code in C
- Code in asm
If you take a look at the C code and pseudo code, you'll notice that any misuse of instructions aside, your logic was wrong (e.g. you needed two strlen
calls before the malloc
)
So, here is your assembler cleaned for style [not much new code]. Notice that I may have broken some of your existing logic, but my version may be easier on the eyes. I used tabs to separate things and got everything to line up. That can help. Also, the comments show intent or note limitations of instructions or architecture.
.global csinsert
csinsert:
stmfd sp!,{v1-v6,lr} // preserve caller registers
// preserve our arguments across calls
mov v1,a1
mov v2,a2
mov v3,a3
// get length of destination string
mov a1,v1 // set dest addr as strlen arg
bl strlen // call strlen
add a1,a1,#1 // increment length
mov v4,a1 // save it
add v3,v3 // src = src + src (what???)
mov v5,v2 // save it
add v3,v3 // double the offset (what???)
bl malloc // get heap memory
mov v4,#0 // set index for loop
loop:
ldrb v7,[v1],#1
subs v2,v2,#1
add v7,v7,a2
strb v7,[a1],#1
bne loop
ldmfd sp!,{v1-v6,pc} @std // restore caller registers
.end
At first, you should prototype in real C:
// csinsert_real -- real C code
char *
csinsert_real(char *s1,char *s2,int loc)
{
int s1len;
int s2len;
char *bp;
int chr;
char *bf;
s1len = strlen(s1);
s2len = strlen(s2);
bf = malloc(s1len + s2len + 1);
bp = bf;
// copy over s1 up to but not including the "insertion" point
for (; loc > 0; --loc, ++s1, ++bp) {
chr = *s1;
if (chr == 0)
break;
*bp = chr;
}
// "insert" the s2 string
for (chr = *s2++; chr != 0; chr = *s2++, ++bp)
*bp = chr;
// copy the remainder of s1 [if any]
for (chr = *s1++; chr != 0; chr = *s1++, ++bp)
*bp = chr;
*bp = 0;
return bf;
}
Then, you can [until you're comfortable with arm], prototype in C "pseudocode":
// csinsert_pseudo -- pseudo arm code
char *
csinsert_pseudo()
{
// save caller registers
v1 = a1;
v2 = a2;
v3 = a3;
a1 = v1;
strlen();
v4 = a1;
a1 = v2;
strlen();
a1 = a1 + v4 + 1;
malloc();
v5 = a1;
// NOTE: load/store may only use r0-r7
// and a1 is r0
#if 0
r0 = a1;
#endif
r1 = v1;
r2 = v2;
// copy over s1 up to but not including the "insertion" point
loop1:
if (v3 == 0) goto eloop1;
r3 = *r1;
if (r3 == 0) goto eloop1;
*r0 = r3;
++r0;
++r1;
--v3;
goto loop1;
eloop1:
// "insert" the s2 string
loop2:
r3 = *r2;
if (r3 == 0) goto eloop2;
*r0 = r3;
++r0;
++r2;
goto loop2;
eloop2:
// copy the remainder of s1 [if any]
loop3:
r3 = *r1;
if (r3 == 0) goto eloop3;
*r0 = r3;
++r0;
++r1;
goto loop3;
eloop3:
*r0 = 0;
a1 = v5;
// restore caller registers
}