Where to learn LC3 with proper full explanations? - lc3

This may seem rather silly, but there actually are hardly any resources to learn LC-3. I can't manage to seem to find a proper in depth analysis of the subject and explain how things work, I mean sure, you can find simple definitions and what certain op/pseudo-op codes do, but nothing written in full and fully explained.
If someone could do a full analysis of the following:
; Hello name in LC-3 assembler
.orig x3000
lea r0, what
puts
lea r1, name
; typical assembly language hack coming up
add r1, r1, #-1
char getc
putc
add r2, r0, #-10
brz completed; was a newline
str r0, r1, #0
add r1, r1, #1
brnzp char
completed lea r0, hello
puts
halt
That would probably extremely lengthy, but also very appreciated. (Maybe this is the first stack post for a full analysis of LC-3 code resource?)
p.s I don't expect the person who answers to explain what each op/pseudo op code does but at least be very specific about how the operator performs and does its work

I mostly learned how LC3 worked from plugging in code and stepping through it. Though I would reference Appendix A in the book a LOT.
; Hello name in LC-3 assembler
.orig x3000 ; Starting place in memory for our code
lea r0, what ; Load the memory address of variable what
puts ; Print the string who's memory address is stored in R0
lea r1, name ; Load the memory address of the variable name into R1
; typical assembly language hack coming up
add r1, r1, #-1 ; Subtract 1 from R1, then store into R1
char getc ; Get a single char from the user, store into R0
putc ; Print that same char to the console
add r2, r0, #-10 ; R2 = R0 - 10
brz completed ; If the user presses ENTER (Ascii 10)
; and we've subtracted 10 then we'll get a 0, exit program
str r0, r1, #0 ; Store the value of R0 into memory[R1 + 0]
add r1, r1, #1 ; R1 = R1 + 1
brnzp char ; Jump to Clear no matter what
completed lea r0, hello ; Load the memory address of variable hello into R0
puts ; Print the string stored in hello
halt ; Stop the program

Related

Understand a piece of assembly template code for arm gcc

Below code contains some inline assembly template:
static inline uintptr_t arch_syscall_invoke3(uintptr_t arg1, uintptr_t arg2,
uintptr_t arg3,
uintptr_t call_id)
{
register uint32_t ret __asm__("r0") = arg1;
register uint32_t r1 __asm__("r1") = arg2;
register uint32_t r2 __asm__("r2") = arg3;
register uint32_t r6 __asm__("r6") = call_id;
__asm__ volatile("svc %[svid]\n"
: "=r"(ret), "=r"(r1), "=r"(r2) <===================== HERE 1
: [svid] "i" (_SVC_CALL_SYSTEM_CALL), <===================== HERE 2
"r" (ret), "r" (r1), "r" (r2), "r" (r6)
: "r8", "memory", "r3", "ip");
return ret;
}
And I got the final assembly with https://godbolt.org/z/znMeEMrEz like this:
push {r6, r7, r8} ------------- A -------------
sub sp, sp, #20
add r7, sp, #0
str r0, [r7, #12]
str r1, [r7, #8]
str r2, [r7, #4]
str r3, [r7]
ldr r0, [r7, #12]
ldr r1, [r7, #8]
ldr r2, [r7, #4]
ldr r6, [r7]
svc #3 ------------- B -------------
mov r3, r0 ------------- C1 -------------
mov r0, r3 ------------- C2 -------------
adds r7, r7, #20
mov sp, r7
pop {r6, r7, r8}
bx lr
From A to B, the assembly code just ensure the input arguments are present in the targeted registers. I guess this is some system call convention.
I don't understand the purpose of HERE 1 and HERE 2.
Question 1:
According to here, HERE 1 should be the OutputOperands part, which means
A comma-separated list of the C variables modified by the instructions in the AssemblerTemplate.
Does this mean the specific requested system call function will modify the ret/r0, r1 and r2regitser?
Question 2:
For HERE 2, it means InputOperands, which means:
A comma-separated list of C expressions read by the instructions in the AssemblerTemplate. An empty list is permitted. See InputOperands.
According to here, the SVC instruction expects only 1 argument imm.
But we specify 4 input operands like ret, r1, r2, r6.
Why do we need to specify so many of them?
I guess these registers are used by svc handler so I need to prepare them before the SVC instruction. But what if I just prepare them like from A to B and do not mention them as the input operands? Will there be some error?
Question 3:
And at last, what's the point of the C1 and C2? They seem totally redundant. The r0 is still there.
I guess this is some system call convention.
This is the result of compilation without optimizations. Looking closely at what's going on in that code one can see that after saving r6, r7 and r8 all it does is moving r3 to r6, everything else is redundant.
Question 1: Does this mean the specific requested system call function will modify the ret/r0, r1 and r2 regitser?
Yes.
Question 2: According to here, the SVC instruction expects only 1 argument imm. But we specify 4 input operands like ret, r, r2, r6.
We specify imm to generate a correct SVC instruction and we specify the rest to make sure that the system call we invoke will find its arguments in the registers documented in the system call ABI.
Why do we need to specify so many of them?
According to the function name it's a 3-argument syscall, so we have 3 syscall parameters and apparently the system call identifier.
But what if I just prepare them like from A to B and do not mention them as the input operands? Will there be some error?
One cannot reliably do the just prepare them like from A to B part without mentioning them as inputs in that asm statement. Just assigning function arguments to local variables is not enough because nothing will enforce the correct ordering of this assignment and the asm statement. There will be no compile-time error unless compiling with warnings-as-errors and having enabled the warning for unused but set variables.
Question 3: And at last, what's the point of the C1 and C2? They seem totally redundant. The r0 is still there.
They are. Compiling with -O will eliminate this redundant move as well as most of the prologue.

Introductory ARM - Assembly Error

For a class just starting out with the ARM assembly language, we are required to implement a simple for-loop described below:
h=1;
for (i=0, i<5, i++)
h=(h*3)-i;
I have written the following code in ARM assembly:
AREA Prog2, CODE, READONLY
ENTRY
MOV r0, #1; initialize h=1
MOV r1, #0; initialize i=0
loop CMP r1, #5; at start of loop, compare i with 5
MULLT r0, r0, #3; if i<5, h=h*3
SUBLT r0, r0, r1; if i<5, h=h-i (ties in with previous line)
ADDLT r1, r1, #1; increment i if i is less than 5
BLT loop ; repeat loop of i is less than 5
stop B stop; stop program
END
The problem is that there is an error with the line
MULLT r0, r0, #3; if i<5, h=h*3
If I delete it from the code, everything works fine. I just cannot understand the issue with this one line. The error description given is "Bad register name symbol, expected integer register." I have tried loading #3 into a register then multiplying the two registers, but that didn't help. It simply changed the error message to "This register combination results in unpredictable behavior." I am new to this, so please offer only basic instructions as a fix for this. Thanks.
MUL requires all operands to be registers, so you must use the form MUL r0, rn, r0 where rn is some other suitable register.
If the result and the first operand are the same the result is unpredictable as the error says. This is due to the internal operation of the processor. This is why you must use r0, rn, r0 and not r0, r0, rn
Multiplying by 3 is overrated anyway; ARM can do it with a single ridiculously idiomatic addition:
add r0, r0, r0 lsl #1 // r0 = r0 + r0 *2

LC3 trap executed illegal vector number

I'm trying to count the number of characters in LC3 simulator and keep getting "a trap was executed with an illegal vector number".
These are the objects I execute
charcount.obj:
0011000000000000
0101010010100000
0010011000010000
1111000000100011
0110001011000000
0001100001111100
0000010000001000
1001001001111111
0001001001100001
0001001001000000
0000101000000001
0001010010100001
0001011011100001
0110001011000000
0000111111110110
0010000000000100
0001000000000010
1111000000100001
1111000000100101
1110001011111111
0000000000110000
and verse:
.ORIG x3100
.STRINGZ "Simple Simon met a pieman,"
.STRINGZ "Going to the fair;"
.STRINGZ "Says Simple Simon to the pieman,"
.STRINGZ "Let me taste your ware."
.FILL x04
.END
Looks like we're going to need more information before we can help you much. I understand you've provided us with some binary and I ran that through the LC3 simulator. Here's where I'm a bit lost, which string would you like to count and where is it stored?
After trying to piece together what you've provided here's what I've found.
Registers:
R0 x0061 97
R1 x0000 0
R2 x0000 0
R3 xE2FF -7425
R4 x0000 0
R5 x0000 0
R6 x0000 0
R7 x3003 12291
PC x3004 12292
IR x62C0 25280
CC Z
Memory:
x3000 0101010010100000 x54A0 AND R2, R2, #0
x3001 0010011000010000 x2610 LD R3, x3012
x3002 1111000000100011 xF023 TRAP IN
x3003 0110001011000000 x62C0 LDR R1, R3, #0
x3004 0001100001111100 x187C ADD R4, R1, #-4
x3005 0000010000001000 x0408 BRZ x300E
x3006 1001001001111111 x927F NOT R1, R1
x3007 0001001001100001 x1261 ADD R1, R1, #1
x3008 0001001001000000 x1240 ADD R1, R1, R0
x3009 0000101000000001 x0A01 BRNP x300B
x300A 0001010010100001 x14A1 ADD R2, R2, #1
x300B 0001011011100001 x16E1 ADD R3, R3, #1
x300C 0110001011000000 x62C0 LDR R1, R3, #0
x300D 0000111111110110 x0FF6 BRNZP x3004
x300E 0010000000000100 x2004 LD R0, x3013
x300F 0001000000000010 x1002 ADD R0, R0, R2
x3010 1111000000100001 xF021 TRAP OUT
x3011 1111000000100101 xF025 TRAP HALT
x3012 1110001011111111 xE2FF LEA R1, x3112
x3013 0000000000110000 x0030 NOP
The values displayed in the registers is what I get when I stop after line x3003. For some reason the literal value of xE2FF gets loaded into register R3. After that the value of 0 at memory location xE2FF is loaded into register R1 and then the problems mount from there.
I would recommend displaying your asm code and then commenting each line so we can better understand what you're trying to accomplish.

LC-3 How do I reset my counter?

Can anyone run this program, or try to help me understand why my counter isnt updating?
I am supposed to read in text from a prompt and find the length of the text and output it with a colon before printing out the actual text entered.
If the first time I type in "test" the length is 4, but then when it loops back to start it asks me to input again, it outputs the correct text, but the counter doesnt change unless the text is longer.
So, if i type "I", it will output length of 4, since test is longer and is 4. But if I type "Control" which is 7 letters, it will update the counter to 7.
OUTPUT:
Enter: Hey
3:Hey
Enter: Test
4:Test
Enter: Control
7:Control
Enter: Hey
7:Hey <---- Length should be 3!
Thank you!
.orig x3000 ; Starting point of the program.
BR start ; Branch to the start routine.
newln: .stringz "\n"
msg1: .stringz "Enter: "
; Prints out the instructions to the user and reads some user input.
start:
lea r0, newln ; Load address of newline into R0.
puts ; Print newline.
lea r0, msg1 ; Load address of message3 into R0.
puts
lea r2, MESSAGE ; Load starting point address of MESSAGE.
and r1, r1, #0 ; Initialize R1 to zero.
input:
getc ; Read in a single character to R0.
out
add r5, r0, #-10 ; Subtract 10 because enter key is 10.
BRz printint ; If zero, branch to checkChar routine.
; Else continue the loop.
str r0, r2, #0 ; Store char in MESSAGE.
add r2, r2, #1 ; Increment index of MESSAGE.
add r1, r1, #1 ; Increment input counter.
BR input ; Unconditional branch to input.
checkChar:
lea r5, inv81 ; Load address of inv68 into R6.
ldr r5, r5, #0 ; Load contents of inv68 into R6 (R6 now holds -68).
add r0, r3, r5 ; Add -68 to the value in R3, to check if it's 'q'.
BRz quit ; If zero, branch to decrypt.
;
;print integer starts here
;
printint:
ld r3,psign
jsr STRLEN
ADD r7, r0, #0 ; get the integer to print
brzp nonneg
ld r3,nsign
not r7,r7
add r7,r7,1
nonneg:
lea r6,buffer ; get the address of o/p area
add r6,r6,#7 ; compute address of end of o/p
ld r5,char0 ; get '0' to add to int digits
loop1:
and r0,r0,#0 ; init quotient for each divide
loop2:
add r7,r7,#-10 ; add -10
brn remdr ; until negative
add r0,r0,#1 ; incr to compute quotient
br loop2 ; repeat
remdr:
add r7,r7,#10 ; add 10 to get remainder
add r7,r7,r5 ; convert to ascii
str r7,r6,0 ; place ascii in o/p
add r7,r0,#0 ; move quot for next divide
brz end ; if done then print
add r6,r6,#-1 ; move to prev o/p position
br loop1 ; repeat
end:
add r6,r6,#-1 ; move to prev o/p position
str r3,r6,0 ; place sign
add r0,r6,#0 ; move address of 1st char
puts ; into r0 and print
output:
ld r5, colon
and r3,r3, 0;
add r0, r3, r5;
out
lea r2, MESSAGE ; Load (starting) address of MESSAGE.
outputLoop:
ldr r0, r2, #0 ; Load contents of address at MESSAGE index into R0.
out ; Print character.
add r2, r2, #1 ; Increment MESSAGE index.
add r1, r1, #-1 ; Decrease counter.
BRp outputLoop ; If positive, loop.
br start
quit:
halt ; Halt execution.
STRLEN:
LEA R2, MESSAGE ;R1 is pointer to characters
AND R0, R0, #0 ;R0 is counter, initially 0
LD R5, char0
LOOP: ADD R2, R2, #1 ;POINT TO NEXT CHARACTER
LDR R4, R2, #0 ;R4 gets character input
BRz FINISH
ADD R0, R0, #1
BR LOOP
FINISH:
ADD R0, R0, #1
ret
MESSAGE: .blkw 99 ; MESSAGE of size 20.
inv48: .fill #-48 ; Constant for converting numbers from ASCII to decimal.
inv81: .fill #-81 ; Constant for the inverse of 'Q'.
buffer: .blkw 8 ; o/p area
null: .fill 0 ; null to end o/p area
char0: .fill x30
colon .fill x3A
nsign .fill x2d
psign .fill x20
.end
At the end of your example, the contents in memory starting at message are:
Heytrol0000000
It looks to me like the problem is that in STRLEN we compute the length of the string by counting until we reach the first character that is 0. There are 7 characters in "Heytrol".
However, when we are storing the message, we count the number of characters that we have read in (kept in r1). When we print the string later on, we use the value in r1, so we don't end up printing any of the "extra" characters.
To fix this, I'd either output the value in r1 that was computed while reading the string as its length (get rid of the STRLEN code completely) or make sure that when we read the enter in the input loop, we write a zero into the string before going and printing:
input:
getc ; Read in a single character to R0.
out
add r5, r0, #-10 ; Subtract 10 because enter key is 10.
BRz finishString ; If zero, branch to finishString routine.
; Else continue the loop.
str r0, r2, #0 ; Store char in MESSAGE.
add r2, r2, #1 ; Increment index of MESSAGE.
add r1, r1, #1 ; Increment input counter.
BR input ; Unconditional branch to input.
finishString:
and r0, r0, #0 ; set r0 to zero so we can store it
str r0, r2, #0 ; write zero (from r0) into the end of the string
BR printint ; Now, branch to checkChar routine.

LC3 Assembly Language

I am trying to write a LC3 assembly language program that takes two input numbers and prints out x * y = z.
I can get it to work for numbers 0-9 however any numbers above that I get weird letters or symbols.
Also how can I make it so that it can not only take only 1 inputs per GETC but two numbers eg. 10 * 12= 120?
Any help would be appreciated! :)
Here's what I have done so far
.ORIG x3000
AND R3, R3, #0 ;r3 stores the sum, set r3 to zero
AND R4, R4, #0 ;r4 is the counter
LD R5, INVERSE_ASCII_OFFSET ;inverse ascii offset
LD R6, DECIMAL_OFFSET ;decimal offset
;---------------------
;storing first input digits
LEA R0, display1 ;load the address of the 'display1' message string
PUTS ;Prints the message string
GETC ;get the first number
OUT ;print the first number
ADD R1, R0, #0 ;store input value(ascii) to r1
ADD R1, R1, R5 ;get real value of r1
;storing second input digits
LEA R0, display2 ;load the address of the 'display2' message string
PUTS ;Prints the message string
GETC ;get the first number
OUT ;print the first number
ADD R2, R0, #0 ;store input value(ascii) to r2
ADD R2, R2, R5 ;get real value of r2
;----------------------
ADD R4, R2, #0 ;fill counter with multiplier
MULTIPLICATION:
ADD R3, R3, R1 ;add to sum
ADD R4, R4, #-1 ;decrease counter by one
BRp MULTIPLICATION ;continue loop until multiplier is 0
LEA R0, stringResult
PUTS
ADD R0, R3, R6 ;move result to r0
OUT ;print result
HALT
display1 .STRINGZ "\nenter the 1st no.: "
display2 .STRINGZ "\nenter the 2nd no.: "
stringResult .STRINGZ "\nResult: "
INVERSE_ASCII_OFFSET .fill xFFD0 ; Negative of x0030.
DECIMAL_OFFSET .fill #48
.END
Your display function works by adding a number to the base ascii value of '0'. This works because the ascii table was arranged in a way to be convenient. For instance, '0' + 1 = '1', which is equivalent to 0x30 + 1 = 0x31. However, if you are probably finding that '0' + 12 = '<'. This is because '0' = 0x30, so 0x30 + 12 (0xC) = 0x3C. Looking at the ascii chart we see that 0x3C = '<'. That is, this is an effective method only to print out a single digit.
The answer to both your questions lie in writing a routine that iteratively deals with digits and forms a number with them. In other words, you will need a loop that determines which character to print out next and prints it.

Resources