Given the file
#include <stdio.h>
int main() {
printf("hello world\n");
return 0;
}
I can use the commands
clang -emit-llvm hello.c -c -o hello.bc
llc hello.bc -march=mipsel -relocation-model=static -o hello.s
to produce a nice bit of what looks like MIPS code, (placed below, to avoid breaking up the text) unfortunately, when I try and run it in my trusty SPIM simulator for MIPS I find that SPIM objects to almost every line of it. Not only the '.Section .mdebug.abi32' lines but also
any line of the form '.cfi*' - and even more confusingly (because it looks like MIPS to me...) the line 'lui $2, %hi(__gnu_local_gp)" is objected to.
I am looking for some information on the different flavours of MIPS that SPIM and LLVM cope with, or someone to give an example of a MIPS simulator I can run that accepts the MIPS code that LLVM is producing.
.Section .mdebug.abi32
.previous
.file "hello.bc"
.text
.globl main
.align 2
.type main,#function
.set nomips16 # #main
.ent main
main:
.cfi_startproc
.frame $sp,32,$ra
.mask 0x80000000,-4
.fmask 0x00000000,0
.set noreorder
.set nomacro
# BB#0: # %entry
addiu $sp, $sp, -32
$tmp2:
.cfi_def_cfa_offset 32
sw $ra, 28($sp) # 4-byte Folded Spill
$tmp3:
.cfi_offset 31, -4
lui $2, %hi(__gnu_local_gp)
addiu $2, $2, %lo(__gnu_local_gp)
sw $2, 16($sp)
sw $zero, 24($sp)
lui $2, %hi($.str)
addiu $4, $2, %lo($.str)
jal printf
nop
addiu $2, $zero, 0
lw $ra, 28($sp) # 4-byte Folded Reload
addiu $sp, $sp, 32
jr $ra
nop
.set macro
.set reorder
.end main
$tmp4:
.size main, ($tmp4)-main
.cfi_endproc
.type $.str,#object # #.str
.section .rodata.str1.1,"aMS",#progbits,1
$.str:
.asciz "hello world\n"
.size $.str, 13
Spim is a simple teaching tool which does not support gnu assembler. You might try using OVPsim, which has complete models of various real MIPS processors. You can run Linux on OVPsim, and you should be able to run a MIPS Linux executable produced by clang on that simulated Linux.
The Mips assembly printer in LLVM emits assembly in GAS format (suitable for consumption by the GNU assembler and compatible tools). There's a very good chance SPIM cannot read that. However, if SPIM could read Mips binaries, you could try to emit an object file from LLVM and let SPIM handle that.
Binaries are more "universal" since they must be understood by the CPU itself. Alas, each assembler usually has its own specific syntax only it understands, and assemblers don't tend to be compatible with each other and disagree on basic things like operator ordering, punctuation semantics, directives, and so on.
Related
I am trying to learn more about compilers and RISC V assembly was specifically designed to be easy to learn and teach. I am interested in compiling some simple C code to assembly using clang for the purpose of understanding the semantics. I'm planning on using venus to step through the assembly and the source code does NOT actually need to be fully compiled to machine code in order to run on a real machine.
I want to avoid compiler optimizations so I can see what I've actually instructed the processor to do.
I don't actually need the program to compile to machine code--I just want the assembly.
I don't want to worry about linking to the system library because this code doesn't actually need to run
The code does not make any explicit use of system calls and so I think a std lib should not be required
This answer seems to indicate that clang definitely can compile to RISC V targets, but it requires having a version of the OS's standard library built for RISC V.
This answer indicates that some form of cross-compiling is necessary, but again I don't need to fully compile the code to machine instructions so this should not apply if I'm understanding correctly.
Use clang -S to stop after generating an assembly file:
$ cat foo.c
int main() { return 2+2; }
$ clang -target riscv64 -S foo.c
$ cat foo.s
.text
.attribute 4, 16
.attribute 5, "rv64i2p0_m2p0_a2p0_c2p0"
.file "foo.c"
.globl main
.p2align 1
.type main,#function
main:
addi sp, sp, -32
sd ra, 24(sp)
sd s0, 16(sp)
addi s0, sp, 32
li a0, 0
sw a0, -20(s0)
li a0, 4
ld ra, 24(sp)
ld s0, 16(sp)
addi sp, sp, 32
ret
.Lfunc_end0:
.size main, .Lfunc_end0-main
.ident "Ubuntu clang version 14.0.0-1ubuntu1"
.section ".note.GNU-stack","",#progbits
.addrsig
You can also use Compiler Explorer conveniently online.
I am porting some 32-bit ARM code to 64-bit and am having trouble determining the 64-bit version of this instruction:
ldr r1, =_fns
Where _fns is a symbol defined in some C source file elsewhere in the project.
I've tried the below but both give errors:
adr x1, _fns <== "error: unknown AArch64 fixup kind!"
adrl x1, _fns <== "error: unrecognized instruction mnemonic"
The assembler is LLVM in the iOS SDK (XCode 7.1).
I've noticed that if _fns is locally defined (i.e. in the same .S file) then "adr x1,_fns" works fine. However that's not a fix as _fns has to be in C code (i.e. in a different translation unit).
What is the right way to do this with LLVM ARM assembler?
If I feed
extern char ar[];
char *f()
{
return ar;
}
into the ELLCC (clang based) demo, I get:
Output from the compiler targeting ARM AArch64
.text
.file "/tmp/webcompile/_3793_0.c"
.globl f
.align 2
.type f,#function
f: // #f
// BB#0: // %entry
adrp x0, ar
add x0, x0, :lo12:ar
ret
.Lfunc_end0:
.size f, .Lfunc_end0-f
.ident "ecc 0.1.13 based on clang version 3.7.0 (trunk) (based on LLVM 3.7.0svn)"
.section ".note.GNU-stack","",#progbits
The adrp instruction gets the "page" address of ar into x0. The symbol argument to adrp translates into a 21 bit PC relative offset to the 4K page in which the symbol resides. That offset is added to the PC to get the actual start of the page. The add instruction adds the low 12 bits of the symbol address to get the actual symbol address.
This instruction sequence allows the address of a symbol within +/-4GB of the PC to be loaded.
As far as I can tell, there doesn't seem to be a way of getting functionality similar to 32 bit ARM's "=ar" in C. I assembly language, it looks like this will work:
.text
.file "atest.s"
.globl f
.align 2
f:
ldr x0, p
ret
.align 3
p:
.xword _fns
This is very similar to what the 32 bit ARM does under the hood.
The only reason I started out with the C version was to show how I usually attack a problem like this, especially if I'm not that familiar with the target assembly language.
this work well for me, xcode7.1 LLVM7.0 IOS9.1
In 32bit arm
ldr r9,=JumpTab
Change to 64bit arm
adrp x9,JumpTab#PAGE
add x9,x9,JumpTab#PAGEOFF
By the way,you need care registers' number, some registers have specific useful in arm64
I get linking problem when create library for iOS 7 on iPhone (ARM64).
The error message is:
ld: in /long_path/libHEVCCodec.a(inv_xforms_arm64.o), in section TEXT,text reloc 0:
ARM64_RELOC_SUBTRACTOR must have r_length of 2 or 3 for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
This error appears as a result to this code (it's some sort of switch):
adr addr, .L.dct_add_switch
ldrh offset, [addr, ta, lsl #1]
add addr, addr, offset, uxth
br addr
.L.dct_add_switch:
.hword .L.dct_add_4 - .L.dct_add_switch
.hword .L.dst_add_4 - .L.dct_add_switch
...
ta, addr, offset are general registers x3, x4, w5 respectively.
Does anybody know how to handle this situation?
PS: there are not any problems with GNU GCC & Android.
EDIT1:
It seems that problem is not in linker itself but in compiler.
I checked object file (objdump) and instead of difference constants there are just zeros.
.L.dct_add_switch:
0000000000000010 .long 0x00000000
0000000000000014 .long 0x00000000
0000000000000018 .long 0x00000000
000000000000001c nop
When I put manually calculated constants instead of ".L.dct_add_4 - .L.dct_add_switch", etc expressions, everything is going all right.
Maybe there is some compiler keys which will make compiler to do it job rightfully?
Thanks.
Well there is a compiler & linker problem and it depends on the size of data which are used for offsets. Clang is not very friendly to anything what is different from 4 Bytes.
The discussion and possible solutions in other topic: creating constant jump table; xcode; clang; asm
The problem is the Mach-O object file format for ARM 64-bit targets doesn't support a relocation for the 16-bit difference between two symbols. It appears that the difference must be 32-bit or 64-bit. It doesn't seem to be a problem with the compiler or the linker. The assembly code you've quoted in your question looks like handcrafted assembly, not compiler output.
The solution would be to rewrite the assembly to use 32-bit difference values. Something like this:
adr addr, .L.dct_add_switch
ldr offset, [addr, ta, lsl #2]
add addr, addr, offset, uxtw
br addr
.L.dct_add_switch:
.word .L.dct_add_4 - .L.dct_add_switch
.word .L.dst_add_4 - .L.dct_add_switch
I am in trouble with the definition 'memory location'. According to the 'Intel 64 and IA-32 Software Developer's Manual' many instruction can use a memory location as operand.
For example MOVBE (move data after swapping bytes):
Instruction: MOVBE m32, r32
The question is now how a memory location is defined;
I tried to use variables defined in the .bss section:
section .bss
memory: resb 4 ;reserve 4 byte
memorylen: equ $-memory
section .text
global _start
_start:
MOV R9D, 0x6162630A
MOV [memory], R9D
SHR [memory], 1
MOVBE [memory], R9D
EDIT:->
MOV EAX, 0x01
MOV EBX, 0x00
int 0x80
<-EDIT
If SHR is commented out yasm (yasm -f elf64 .asm) compiles without problems but when executing stdio shows: Illegal Instruction
And if MOVBE is commented out the following error occurs when compiling: error: invalid size for operand 1
How do I have to allocate memory for using the 'm' option shown by the instruction set reference?
[CPU=x64, Compiler=yasm]
If that is all your code, you are falling off at the end into uninitialized region, so you will get a fault. That has nothing to do with allocating memory, which you did right. You need to add code to terminate your program using an exit system call, or at least put an endless loop so you avoid the fault (kill your program using ctrl+c or equivalent).
Update: While the above is true, the illegal instruction here is more likely caused by the fact that your cpu simply does not support the MOVBE instruction, because not all do. If you look in the reference, you can see it says #UD If CPUID.01H:ECX.MOVBE[bit 22] = 0. That is trying to tell you that a particular flag bit in the ECX register returned by the 01 leaf of the CPUID instruction shows support of this instruction. If you are on linux, you can conveniently check in /proc/cpuinfo whether you have the movbe flag or not.
As for the invalid operand size: you should generally specify the operand size when it can not be deduced from the instruction. That said, SHR accepts all sizes (byte, word, dword, qword) so you should really not get that error at all, but you might get an operation of unexpected default size. You should use SHR dword [memory], 1 in this case, and that also makes yasm happy.
Oh, and +1 for reading the intel manual ;)
I am new to this and here is what I want to do -
Create simple programs - loops, counters etc. using ARMv7 assembly
I want to be able to compile them on the Mac / Win / Linux for running on the iPhone
I have a jailbroken iPhone so I can upload the file there, sign it with ldid and run it
Can someone please point me to how I can do this with freely available tools?
Thanks!
I'm left wondering what you're trying to achieve here - is it to learn developing in ARM assembler? Do you want to write iOS applications?
No sane person writes complete applications in assembler these days - they use high level languages - and in a few restricted cases optimize in assembler. This is a very specialist and useful skill to have.
Using a complete C program as a surrogate host is good way to start. Create yourself a simple Hello world program in C which calls an (almost) empty function.
You can (mostly) get this to work using XCode (you need install the optional command-line tools). All but the final linking stage for ARM works using clang. This is obviously MacOSX only.
A better alternative for this kind of experimentation is an ARM Linux system where you're not fighting against the locked down environment of iOS. The Raspberry Pi is perfect for the job. You'll need a cross-compiling toolchain for ARMv7 - of which there are plenty. If using Ubuntu, there are pre-built packages readily available.
Main.c
#include<stdio.h>
extern void func();
int main()
{
printf("Hello World\n");
func();
}
and func.c
#include <stdio.h>
void func()
{
printf("In func()\n");
}
Compile both for your host environment and run it to see it works:
gcc main.c func.c
`./a.out'
Now compile for your target environment. The precise name of the cross-compiling tools varies depending what you installed (mine is arm-angstrom-linux-gnueabi-gcc)
arm-angstrom-linux-gnueabi-gcc main.c func.c
Copy to your target, and prove it works.
Now you can start to write some assembler. Get gcc to produce ARM assembler for our victim file func.c - this results in a file func.s
arm-angstrom-linux-gnueabi-gcc func.c -s
:
.cpu arm7tdmi-s
.fpu softvfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 18, 4
.file "func.c"
.section .rodata
.align 2
.LC0:
.ascii "In func()\000"
.text
.align 2
.global func
.type func, %function
func:
# Function supports interworking.
# args = 0, pretend = 0, frame = 0
# frame_needed = 1, uses_anonymous_args = 0
stmfd sp!, {fp, lr}
add fp, sp, #4
ldr r0, .L2
bl puts
sub sp, fp, #4
ldmfd sp!, {fp, lr}
bx lr
.L3:
.align 2
.L2:
.word .LC0
.size func, .-func
.ident "GCC: (GNU) 4.5.4 20120305 (prerelease)"
.section .note.GNU-stack,"",%progbits
You can see here that between label func: and .L3 is the business end of func() - and it's almost all function prologue and epilogue. You'll want to check out the ARM Procedure Call Standard to understand what these are and for guidance on which registers to use.
Once you've done your edits, compile the whole thing again with GCC
arm-angstrom-linux-gnueabi-gcc main.c func.s
...and test it.