mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2026-01-27 04:01:36 +01:00
userland: add assembly support
Move arm assembly cheat here, and start some work on x86 cheat as well.
This commit is contained in:
762
README.adoc
762
README.adoc
@@ -422,7 +422,7 @@ index af583ce578..3cc341f303 100644
|
|||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
....
|
....
|
||||||
|
|
||||||
Finally, rebuild Binutils, userland and test our program with <<user-mode-setup>>:
|
Finally, rebuild Binutils, userland and test our program with <<user-mode-simulation>>:
|
||||||
|
|
||||||
....
|
....
|
||||||
./build-buildroot -- host-binutils-rebuild
|
./build-buildroot -- host-binutils-rebuild
|
||||||
@@ -438,7 +438,7 @@ Tested on b60784d59bee993bf0de5cde6c6380dd69420dda + 1.
|
|||||||
|
|
||||||
OK, now time to hack GCC.
|
OK, now time to hack GCC.
|
||||||
|
|
||||||
For convenience, let's use the <<user-mode-setup>>.
|
For convenience, let's use the <<user-mode-simulation>>.
|
||||||
|
|
||||||
If we run the program link:userland/gcc_hack.c[]:
|
If we run the program link:userland/gcc_hack.c[]:
|
||||||
|
|
||||||
@@ -929,6 +929,115 @@ sudo rmmod hello.ko
|
|||||||
dmesg
|
dmesg
|
||||||
....
|
....
|
||||||
|
|
||||||
|
=== Userland setup
|
||||||
|
|
||||||
|
==== About the userland setup
|
||||||
|
|
||||||
|
In order to test the kernel and emulators, userland content in the form of executables and scripts is of course required, and we store it mostly under:
|
||||||
|
|
||||||
|
* link:userland/[]
|
||||||
|
* <<rootfs_overlay>>
|
||||||
|
* <<add-new-buildroot-packages>>
|
||||||
|
|
||||||
|
When we started this repository, it only contained content that interacted very closely with the kernel, or that had required performance analysis.
|
||||||
|
|
||||||
|
However, we soon started to notice that this had an increasing overlap with other userland test repositories: we were duplicating build and test infrastructure and even some examples.
|
||||||
|
|
||||||
|
Therefore, we decided to consolidate other userland tutorials that we had scattered around into this repository.
|
||||||
|
|
||||||
|
Notable userland content included / moving into this repository includes:
|
||||||
|
|
||||||
|
* <<arm-userland>>
|
||||||
|
* <<x86-userland>>
|
||||||
|
* <<c>>
|
||||||
|
* <<cpp>>
|
||||||
|
* <<posix>>
|
||||||
|
* https://github.com/cirosantilli/algorithm-cheat will be good to move here for performance analysis
|
||||||
|
|
||||||
|
==== Userland setup getting started
|
||||||
|
|
||||||
|
There are several ways to run our userland content, notably:
|
||||||
|
|
||||||
|
* natively on the host as shown at: <<userland-setup-getting-started-natively>>
|
||||||
|
+
|
||||||
|
Can only run examples compatible with your host architecture and OS, but has the fastest setup and runtimes.
|
||||||
|
* from user mode simulation as shown at: <<qemu-user-mode-getting-started>>
|
||||||
|
+
|
||||||
|
Can run most examples, with the notable exception of examples that rely on kernel modules.
|
||||||
|
* from full system simulation as shown at: <<qemu-buildroot-setup-getting-started>>.
|
||||||
|
+
|
||||||
|
This is the most reproducible and controlled environment, and all examples work there. But also the slower one to setup.
|
||||||
|
|
||||||
|
===== Userland setup getting started natively
|
||||||
|
|
||||||
|
With this setup, we will use the host toolchain and execute executables directly on the host.
|
||||||
|
|
||||||
|
No installation or toolchain build is reuqired, so you can just jump straight into it.
|
||||||
|
|
||||||
|
Build, run and example, and clean it in-tree with:
|
||||||
|
|
||||||
|
....
|
||||||
|
cd userland
|
||||||
|
./build
|
||||||
|
./c/hello.out
|
||||||
|
./build --clean
|
||||||
|
....
|
||||||
|
|
||||||
|
Source: link:userland/c/hello.c[].
|
||||||
|
|
||||||
|
Or build just one directory:
|
||||||
|
|
||||||
|
....
|
||||||
|
./build c
|
||||||
|
....
|
||||||
|
|
||||||
|
or just one executable:
|
||||||
|
|
||||||
|
....
|
||||||
|
./build c/hello
|
||||||
|
....
|
||||||
|
|
||||||
|
Do a more clean out of tree build and run the program instead:
|
||||||
|
|
||||||
|
....
|
||||||
|
./build-userland --gcc-which host --userland-build-id host
|
||||||
|
"$(./getvar --userland-build-id host userland_build_dir)/hello.out"
|
||||||
|
....
|
||||||
|
|
||||||
|
===== Userland setup getting started full system
|
||||||
|
|
||||||
|
First ensure that <<qemu-buildroot-setup>> is working.
|
||||||
|
|
||||||
|
After doing that setup, you can already execute your userland programs from inside QEMU: the only missing step is how to rebuild executables and run them.
|
||||||
|
|
||||||
|
And the answer is exactly analogous to what is shown at: <<your-first-kernel-module-hack>>
|
||||||
|
|
||||||
|
For example, if we modify link:userland/c/hello.c[] to print out something different, we can just rebuild it with:
|
||||||
|
|
||||||
|
....
|
||||||
|
./build-userland
|
||||||
|
....
|
||||||
|
|
||||||
|
Source: link:build-userland[]. `./build` calls that script automatically for us when doing the initial full build.
|
||||||
|
|
||||||
|
Now, run the program either without rebooting use the <<9p>> mount:
|
||||||
|
|
||||||
|
....
|
||||||
|
/mnt/9p/out_rootfs_overlay/c/hello.out
|
||||||
|
....
|
||||||
|
|
||||||
|
or shutdown QEMU, add the executable to the root filesystem:
|
||||||
|
|
||||||
|
....
|
||||||
|
./build-buildroot
|
||||||
|
....
|
||||||
|
|
||||||
|
reboot and use the root filesystem as usual:
|
||||||
|
|
||||||
|
....
|
||||||
|
/c/hello.out
|
||||||
|
....
|
||||||
|
|
||||||
=== Baremetal setup
|
=== Baremetal setup
|
||||||
|
|
||||||
==== About the baremetal setup
|
==== About the baremetal setup
|
||||||
@@ -1076,8 +1185,8 @@ But just stick to newer and better `VExpress_GEM5_V1` unless you have a good rea
|
|||||||
|
|
||||||
When doing bare metal programming, it is likely that you will want to learn assembly language basics. Have a look at these tutorials for the userland part:
|
When doing bare metal programming, it is likely that you will want to learn assembly language basics. Have a look at these tutorials for the userland part:
|
||||||
|
|
||||||
* https://github.com/cirosantilli/x86-assembly-cheat
|
* <<x86-userland>>
|
||||||
* https://github.com/cirosantilli/arm-assembly-cheat
|
* <<arm-userland>>
|
||||||
|
|
||||||
For more information on baremetal, see the section: <<baremetal>>.
|
For more information on baremetal, see the section: <<baremetal>>.
|
||||||
|
|
||||||
@@ -1086,14 +1195,6 @@ The following subjects are particularly important:
|
|||||||
* <<tracing>>
|
* <<tracing>>
|
||||||
* <<baremetal-gdb-step-debug>>
|
* <<baremetal-gdb-step-debug>>
|
||||||
|
|
||||||
=== User mode setup
|
|
||||||
|
|
||||||
Much like <<baremetal-setup>>, this is another fun setup that does not require Buildroot or the Linux kernel.
|
|
||||||
|
|
||||||
Getting started at: <<qemu-user-mode-getting-started>>.
|
|
||||||
|
|
||||||
Introduction at: <<user-mode-simulation>>.
|
|
||||||
|
|
||||||
[[gdb]]
|
[[gdb]]
|
||||||
== GDB step debug
|
== GDB step debug
|
||||||
|
|
||||||
@@ -1700,7 +1801,7 @@ since GDB does not know that libc is loaded.
|
|||||||
|
|
||||||
This is the userland debug setup most likely to work, since at init time there is only one userland executable running.
|
This is the userland debug setup most likely to work, since at init time there is only one userland executable running.
|
||||||
|
|
||||||
For executables from the <<userland-directory>> such as link:userland/count.c[]:
|
For executables from the link:userland/[] directory such as link:userland/count.c[]:
|
||||||
|
|
||||||
* Shell 1:
|
* Shell 1:
|
||||||
+
|
+
|
||||||
@@ -3288,7 +3389,7 @@ qw er
|
|||||||
|
|
||||||
`./run --userland` path resolution is analogous to <<baremetal-setup-getting-started,that of `./run --baremetal`>>.
|
`./run --userland` path resolution is analogous to <<baremetal-setup-getting-started,that of `./run --baremetal`>>.
|
||||||
|
|
||||||
`./build user-mode-qemu` first builds Buildroot, and then runs `./build-userland`, which is further documented at: <<userland-directory>>. It also builds QEMU. If you ahve already done a <<qemu-buildroot-setup>> previously, this will be very fast.
|
`./build user-mode-qemu` first builds Buildroot, and then runs `./build-userland`, which is further documented at: <<userland-setup>>. It also builds QEMU. If you ahve already done a <<qemu-buildroot-setup>> previously, this will be very fast.
|
||||||
|
|
||||||
If you modify the userland programs, rebuild simply with:
|
If you modify the userland programs, rebuild simply with:
|
||||||
|
|
||||||
@@ -12033,6 +12134,295 @@ make CROSS_COMPILE_DIR=/usr/bin
|
|||||||
;
|
;
|
||||||
....
|
....
|
||||||
|
|
||||||
|
== C
|
||||||
|
|
||||||
|
Programs under link:userland/c/[] are examples of link:https://en.wikipedia.org/wiki/ANSI_C[ANSI C] programming.
|
||||||
|
|
||||||
|
[[cpp]]
|
||||||
|
== C++
|
||||||
|
|
||||||
|
Programs under link:userland/cpp/[] are examples of link:https://en.wikipedia.org/wiki/C%2B%2B#Standardization[ISO C] programming.
|
||||||
|
|
||||||
|
== POSIX
|
||||||
|
|
||||||
|
Programs under link:userland/posix/[] are examples of POSIX C programming.
|
||||||
|
|
||||||
|
What is POSIX:
|
||||||
|
|
||||||
|
* https://stackoverflow.com/questions/1780599/what-is-the-meaning-of-posix/31865755#31865755
|
||||||
|
* https://unix.stackexchange.com/questions/11983/what-exactly-is-posix/220877#220877
|
||||||
|
|
||||||
|
== x86 userland
|
||||||
|
|
||||||
|
Programs under link:userland/arch/x86_64/[] are examples of x86 userland assembly programming.
|
||||||
|
|
||||||
|
Those examples are progressively being moved out of: https://github.com/cirosantilli/x86-assembly-cheat
|
||||||
|
|
||||||
|
== arm userland
|
||||||
|
|
||||||
|
Programs under:
|
||||||
|
|
||||||
|
* link:userland/arch/arm/[]
|
||||||
|
* link:userland/arch/aarch64/[]
|
||||||
|
|
||||||
|
are examples of ARM userland assembly programming.
|
||||||
|
|
||||||
|
== Android
|
||||||
|
|
||||||
|
Remember: Android AOSP is a huge undocumented piece of bloatware. It's integration into this repo will likely never be super good.
|
||||||
|
|
||||||
|
Verbose setup description: https://stackoverflow.com/questions/1809774/how-to-compile-the-android-aosp-kernel-and-test-it-with-the-android-emulator/48310014#48310014
|
||||||
|
|
||||||
|
Download, build and run with the prebuilt AOSP QEMU emulator and the AOSP kernel:
|
||||||
|
|
||||||
|
....
|
||||||
|
./build-android \
|
||||||
|
--android-base-dir /path/to/your/hd \
|
||||||
|
--android-version 8.1.0_r60 \
|
||||||
|
download \
|
||||||
|
build \
|
||||||
|
;
|
||||||
|
./run-android \
|
||||||
|
--android-base-dir /path/to/your/hd \
|
||||||
|
--android-version 8.1.0_r60 \
|
||||||
|
;
|
||||||
|
....
|
||||||
|
|
||||||
|
Sources:
|
||||||
|
|
||||||
|
* link:build-android[]
|
||||||
|
* link:run-android[]
|
||||||
|
|
||||||
|
TODO how to hack the AOSP kernel, userland and emulator?
|
||||||
|
|
||||||
|
Other archs work as well as usual with `--arch` parameter. However, running in non-x86 is very slow due to the lack of KVM.
|
||||||
|
|
||||||
|
Tested on: `8.1.0_r60`.
|
||||||
|
|
||||||
|
=== Android image structure
|
||||||
|
|
||||||
|
https://source.android.com/devices/bootloader/partitions-images
|
||||||
|
|
||||||
|
The messy AOSP generates a ton of images instead of just one.
|
||||||
|
|
||||||
|
When the emulator launches, we can see them through QEMU `-drive` arguments:
|
||||||
|
|
||||||
|
....
|
||||||
|
emulator: argv[21] = "-initrd"
|
||||||
|
emulator: argv[22] = "/data/aosp/8.1.0_r60/out/target/product/generic_x86_64/ramdisk.img"
|
||||||
|
emulator: argv[23] = "-drive"
|
||||||
|
emulator: argv[24] = "if=none,index=0,id=system,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/system-qemu.img,read-only"
|
||||||
|
emulator: argv[25] = "-device"
|
||||||
|
emulator: argv[26] = "virtio-blk-pci,drive=system,iothread=disk-iothread,modern-pio-notify"
|
||||||
|
emulator: argv[27] = "-drive"
|
||||||
|
emulator: argv[28] = "if=none,index=1,id=cache,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/cache.img.qcow2,overlap-check=none,cache=unsafe,l2-cache-size=1048576"
|
||||||
|
emulator: argv[29] = "-device"
|
||||||
|
emulator: argv[30] = "virtio-blk-pci,drive=cache,iothread=disk-iothread,modern-pio-notify"
|
||||||
|
emulator: argv[31] = "-drive"
|
||||||
|
emulator: argv[32] = "if=none,index=2,id=userdata,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/userdata-qemu.img.qcow2,overlap-check=none,cache=unsafe,l2-cache-size=1048576"
|
||||||
|
emulator: argv[33] = "-device"
|
||||||
|
emulator: argv[34] = "virtio-blk-pci,drive=userdata,iothread=disk-iothread,modern-pio-notify"
|
||||||
|
emulator: argv[35] = "-drive"
|
||||||
|
emulator: argv[36] = "if=none,index=3,id=encrypt,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/encryptionkey.img.qcow2,overlap-check=none,cache=unsafe,l2-cache-size=1048576"
|
||||||
|
emulator: argv[37] = "-device"
|
||||||
|
emulator: argv[38] = "virtio-blk-pci,drive=encrypt,iothread=disk-iothread,modern-pio-notify"
|
||||||
|
emulator: argv[39] = "-drive"
|
||||||
|
emulator: argv[40] = "if=none,index=4,id=vendor,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/vendor-qemu.img,read-only"
|
||||||
|
emulator: argv[41] = "-device"
|
||||||
|
emulator: argv[42] = "virtio-blk-pci,drive=vendor,iothread=disk-iothread,modern-pio-notify"
|
||||||
|
....
|
||||||
|
|
||||||
|
The root directory is the <<initrd>> given on the QEMU CLI, which `/proc/mounts` reports at:
|
||||||
|
|
||||||
|
....
|
||||||
|
rootfs on / type rootfs (ro,seclabel,size=886392k,nr_inodes=221598)
|
||||||
|
....
|
||||||
|
|
||||||
|
This contains the <<android-init>>, which through `.rc` must be mounting mounts the drives int o the right places TODO find exact point.
|
||||||
|
|
||||||
|
The drive order is:
|
||||||
|
|
||||||
|
....
|
||||||
|
system
|
||||||
|
cache
|
||||||
|
userdata
|
||||||
|
encryptionkey
|
||||||
|
vendor-qemu
|
||||||
|
....
|
||||||
|
|
||||||
|
Then, on the terminal:
|
||||||
|
|
||||||
|
....
|
||||||
|
mount | grep vd
|
||||||
|
....
|
||||||
|
|
||||||
|
gives:
|
||||||
|
|
||||||
|
....
|
||||||
|
/dev/block/vda1 on /system type ext4 (ro,seclabel,relatime,data=ordered)
|
||||||
|
/dev/block/vde1 on /vendor type ext4 (ro,seclabel,relatime,data=ordered)
|
||||||
|
/dev/block/vdb on /cache type ext4 (rw,seclabel,nosuid,nodev,noatime,errors=panic,data=ordered)
|
||||||
|
....
|
||||||
|
|
||||||
|
and we see that the order of `vda`, `vdb`, etc. matches that in which `-drive` were given to QEMU.
|
||||||
|
|
||||||
|
Tested on: `8.1.0_r60`.
|
||||||
|
|
||||||
|
==== Android images read-only
|
||||||
|
|
||||||
|
From `mount`, we can see that some of the mounted images are `ro`.
|
||||||
|
|
||||||
|
Basically, every image that was given to QEMU as qcow2 is writable, and that qcow2 is an overlay over the actual original image.
|
||||||
|
|
||||||
|
In order to make `/system` and `/vendor` writable by using qcow2 for them as well, we must use the `-writable-system` option:
|
||||||
|
|
||||||
|
....
|
||||||
|
./run-android -- -writable-system
|
||||||
|
....
|
||||||
|
|
||||||
|
* https://android.stackexchange.com/questions/110927/how-to-mount-system-rewritable-or-read-only-rw-ro/207200#207200
|
||||||
|
* https://stackoverflow.com/questions/13089694/adb-remount-permission-denied-but-able-to-access-super-user-in-shell-android/43163693#43163693
|
||||||
|
|
||||||
|
then:
|
||||||
|
|
||||||
|
....
|
||||||
|
su
|
||||||
|
mount -o rw,remount /system
|
||||||
|
date >/system/a
|
||||||
|
....
|
||||||
|
|
||||||
|
Now reboot, and relaunch with `-writable-system` once again to pick up the modified qcow2 images:
|
||||||
|
|
||||||
|
....
|
||||||
|
./run-android -- -writable-system
|
||||||
|
....
|
||||||
|
|
||||||
|
and the newly created file is still there:
|
||||||
|
|
||||||
|
....
|
||||||
|
date >/system/a
|
||||||
|
....
|
||||||
|
|
||||||
|
`/system` and `/vendor` can be nuked quickly with:
|
||||||
|
|
||||||
|
....
|
||||||
|
./build-android --extra-args snod
|
||||||
|
./build-android --extra-args vnod
|
||||||
|
....
|
||||||
|
|
||||||
|
as mentioned at: https://stackoverflow.com/questions/29023406/how-to-just-build-android-system-image and on:
|
||||||
|
|
||||||
|
....
|
||||||
|
./build-android --extra-args help
|
||||||
|
....
|
||||||
|
|
||||||
|
Tested on: `8.1.0_r60`.
|
||||||
|
|
||||||
|
==== Android /data partition
|
||||||
|
|
||||||
|
When I install an app like F-Droid, it goes under `/data` according to:
|
||||||
|
|
||||||
|
....
|
||||||
|
find / -iname '*fdroid*'
|
||||||
|
....
|
||||||
|
|
||||||
|
and it <<disk-persistency,persists across boots>>.
|
||||||
|
|
||||||
|
`/data` is behind a RW LVM device:
|
||||||
|
|
||||||
|
....
|
||||||
|
/dev/block/dm-0 on /data type ext4 (rw,seclabel,nosuid,nodev,noatime,errors=panic,data=ordered)
|
||||||
|
....
|
||||||
|
|
||||||
|
but TODO I can't find where it comes from since I don't have the CLI tools mentioned at:
|
||||||
|
|
||||||
|
* https://superuser.com/questions/131519/what-is-this-dm-0-device
|
||||||
|
* https://unix.stackexchange.com/questions/185057/where-does-lvm-store-its-configuration
|
||||||
|
|
||||||
|
However, by looking at:
|
||||||
|
|
||||||
|
....
|
||||||
|
./run-android -- -help
|
||||||
|
....
|
||||||
|
|
||||||
|
we see:
|
||||||
|
|
||||||
|
....
|
||||||
|
-data <file> data image (default <datadir>/userdata-qemu.img
|
||||||
|
....
|
||||||
|
|
||||||
|
which confirms the suspicion that this data goes in `userdata-qemu.img`.
|
||||||
|
|
||||||
|
To reset images to their original state, just remove the qcow2 overlay and regenerate it: https://stackoverflow.com/questions/54446680/how-to-reset-the-userdata-image-when-building-android-aosp-and-running-it-on-the
|
||||||
|
|
||||||
|
Tested on: `8.1.0_r60`.
|
||||||
|
|
||||||
|
=== Install Android apps
|
||||||
|
|
||||||
|
I don't know how to download files from the web on Vanilla android, the default browser does not download anything, and there is no `wget`:
|
||||||
|
|
||||||
|
* https://android.stackexchange.com/questions/6984/how-to-download-files-from-the-web-in-the-android-browser
|
||||||
|
* https://stackoverflow.com/questions/26775079/wget-in-android-terminal
|
||||||
|
|
||||||
|
Installing with `adb install` does however work: https://stackoverflow.com/questions/7076240/install-an-apk-file-from-command-prompt
|
||||||
|
|
||||||
|
link:https://f-droid.org[F-Droid] installed fine like that, however it does not have permission to install apps: https://www.maketecheasier.com/install-apps-from-unknown-sources-android/
|
||||||
|
|
||||||
|
And the `Settings` app crashes so I can't change it, logcat contains:
|
||||||
|
|
||||||
|
....
|
||||||
|
No service published for: wifip2p
|
||||||
|
....
|
||||||
|
|
||||||
|
which is mentioned at: https://stackoverflow.com/questions/47839955/android-8-settings-app-crashes-on-emulator-with-clean-aosp-build
|
||||||
|
|
||||||
|
We also tried to enable it from the command line with:
|
||||||
|
|
||||||
|
....
|
||||||
|
settings put secure install_non_market_apps 1
|
||||||
|
....
|
||||||
|
|
||||||
|
as mentioned at: https://android.stackexchange.com/questions/77280/allow-unknown-sources-from-terminal-without-going-to-settings-app but it didn't work either.
|
||||||
|
|
||||||
|
No person alive seems to know how to pre-install apps on AOSP: https://stackoverflow.com/questions/6249458/pre-installing-android-application
|
||||||
|
|
||||||
|
Tested on: `8.1.0_r60`.
|
||||||
|
|
||||||
|
=== Android init
|
||||||
|
|
||||||
|
For Linux in general, see: <<init>>.
|
||||||
|
|
||||||
|
The `/init` executable interprets the `/init.rc` files, which is in a custom Android init system language: https://android.googlesource.com/platform/system/core/+/ee0e63f71d90537bb0570e77aa8a699cc222cfaf/init/README.md
|
||||||
|
|
||||||
|
The top of that file then sources other `.rc` files present on the root directory:
|
||||||
|
|
||||||
|
....
|
||||||
|
import /init.environ.rc
|
||||||
|
import /init.usb.rc
|
||||||
|
import /init.${ro.hardware}.rc
|
||||||
|
import /vendor/etc/init/hw/init.${ro.hardware}.rc
|
||||||
|
import /init.usb.configfs.rc
|
||||||
|
import /init.${ro.zygote}.rc
|
||||||
|
....
|
||||||
|
|
||||||
|
TODO: how is `ro.hardware` determined? https://stackoverflow.com/questions/20572781/android-boot-where-is-the-init-hardware-rc-read-in-init-c-where-are-servic It is a system property and can be obtained with:
|
||||||
|
|
||||||
|
....
|
||||||
|
getprop ro.hardware
|
||||||
|
....
|
||||||
|
|
||||||
|
This gives:
|
||||||
|
|
||||||
|
....
|
||||||
|
ranchu
|
||||||
|
....
|
||||||
|
|
||||||
|
which is the codename for the QEMU virtual platform we are running on: https://www.oreilly.com/library/view/android-system-programming/9781787125360/9736a97c-cd09-40c3-b14d-955717648302.xhtml
|
||||||
|
|
||||||
|
TODO: is it possible to add a custom `.rc` file without modifying the initrd that <<android-image-structure,gets mounted on root>>? https://stackoverflow.com/questions/9768103/make-persistent-changes-to-init-rc
|
||||||
|
|
||||||
|
Tested on: `8.1.0_r60`.
|
||||||
|
|
||||||
== Benchmark this repo
|
== Benchmark this repo
|
||||||
|
|
||||||
TODO: didn't fully port during refactor after 3b0a343647bed577586989fb702b760bd280844a. Reimplementing should not be hard.
|
TODO: didn't fully port during refactor after 3b0a343647bed577586989fb702b760bd280844a. Reimplementing should not be hard.
|
||||||
@@ -12316,266 +12706,6 @@ gem5:
|
|||||||
** https://stackoverflow.com/questions/47997565/gem5-system-requirements-for-decent-performance/48941793#48941793
|
** https://stackoverflow.com/questions/47997565/gem5-system-requirements-for-decent-performance/48941793#48941793
|
||||||
** https://github.com/gem5/gem5/issues/25
|
** https://github.com/gem5/gem5/issues/25
|
||||||
|
|
||||||
== WIP
|
|
||||||
|
|
||||||
Big new features that are not yet working.
|
|
||||||
|
|
||||||
=== Android
|
|
||||||
|
|
||||||
Remember: Android AOSP is a huge undocumented piece of bloatware. It's integration into this repo will likely never be super good.
|
|
||||||
|
|
||||||
Verbose setup description: https://stackoverflow.com/questions/1809774/how-to-compile-the-android-aosp-kernel-and-test-it-with-the-android-emulator/48310014#48310014
|
|
||||||
|
|
||||||
Download, build and run with the prebuilt AOSP QEMU emulator and the AOSP kernel:
|
|
||||||
|
|
||||||
....
|
|
||||||
./build-android \
|
|
||||||
--android-base-dir /path/to/your/hd \
|
|
||||||
--android-version 8.1.0_r60 \
|
|
||||||
download \
|
|
||||||
build \
|
|
||||||
;
|
|
||||||
./run-android \
|
|
||||||
--android-base-dir /path/to/your/hd \
|
|
||||||
--android-version 8.1.0_r60 \
|
|
||||||
;
|
|
||||||
....
|
|
||||||
|
|
||||||
Sources:
|
|
||||||
|
|
||||||
* link:build-android[]
|
|
||||||
* link:run-android[]
|
|
||||||
|
|
||||||
TODO how to hack the AOSP kernel, userland and emulator?
|
|
||||||
|
|
||||||
Other archs work as well as usual with `--arch` parameter. However, running in non-x86 is very slow due to the lack of KVM.
|
|
||||||
|
|
||||||
Tested on: `8.1.0_r60`.
|
|
||||||
|
|
||||||
==== Android image structure
|
|
||||||
|
|
||||||
https://source.android.com/devices/bootloader/partitions-images
|
|
||||||
|
|
||||||
The messy AOSP generates a ton of images instead of just one.
|
|
||||||
|
|
||||||
When the emulator launches, we can see them through QEMU `-drive` arguments:
|
|
||||||
|
|
||||||
....
|
|
||||||
emulator: argv[21] = "-initrd"
|
|
||||||
emulator: argv[22] = "/data/aosp/8.1.0_r60/out/target/product/generic_x86_64/ramdisk.img"
|
|
||||||
emulator: argv[23] = "-drive"
|
|
||||||
emulator: argv[24] = "if=none,index=0,id=system,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/system-qemu.img,read-only"
|
|
||||||
emulator: argv[25] = "-device"
|
|
||||||
emulator: argv[26] = "virtio-blk-pci,drive=system,iothread=disk-iothread,modern-pio-notify"
|
|
||||||
emulator: argv[27] = "-drive"
|
|
||||||
emulator: argv[28] = "if=none,index=1,id=cache,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/cache.img.qcow2,overlap-check=none,cache=unsafe,l2-cache-size=1048576"
|
|
||||||
emulator: argv[29] = "-device"
|
|
||||||
emulator: argv[30] = "virtio-blk-pci,drive=cache,iothread=disk-iothread,modern-pio-notify"
|
|
||||||
emulator: argv[31] = "-drive"
|
|
||||||
emulator: argv[32] = "if=none,index=2,id=userdata,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/userdata-qemu.img.qcow2,overlap-check=none,cache=unsafe,l2-cache-size=1048576"
|
|
||||||
emulator: argv[33] = "-device"
|
|
||||||
emulator: argv[34] = "virtio-blk-pci,drive=userdata,iothread=disk-iothread,modern-pio-notify"
|
|
||||||
emulator: argv[35] = "-drive"
|
|
||||||
emulator: argv[36] = "if=none,index=3,id=encrypt,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/encryptionkey.img.qcow2,overlap-check=none,cache=unsafe,l2-cache-size=1048576"
|
|
||||||
emulator: argv[37] = "-device"
|
|
||||||
emulator: argv[38] = "virtio-blk-pci,drive=encrypt,iothread=disk-iothread,modern-pio-notify"
|
|
||||||
emulator: argv[39] = "-drive"
|
|
||||||
emulator: argv[40] = "if=none,index=4,id=vendor,file=/path/to/aosp/8.1.0_r60/out/target/product/generic_x86_64/vendor-qemu.img,read-only"
|
|
||||||
emulator: argv[41] = "-device"
|
|
||||||
emulator: argv[42] = "virtio-blk-pci,drive=vendor,iothread=disk-iothread,modern-pio-notify"
|
|
||||||
....
|
|
||||||
|
|
||||||
The root directory is the <<initrd>> given on the QEMU CLI, which `/proc/mounts` reports at:
|
|
||||||
|
|
||||||
....
|
|
||||||
rootfs on / type rootfs (ro,seclabel,size=886392k,nr_inodes=221598)
|
|
||||||
....
|
|
||||||
|
|
||||||
This contains the <<android-init>>, which through `.rc` must be mounting mounts the drives int o the right places TODO find exact point.
|
|
||||||
|
|
||||||
The drive order is:
|
|
||||||
|
|
||||||
....
|
|
||||||
system
|
|
||||||
cache
|
|
||||||
userdata
|
|
||||||
encryptionkey
|
|
||||||
vendor-qemu
|
|
||||||
....
|
|
||||||
|
|
||||||
Then, on the terminal:
|
|
||||||
|
|
||||||
....
|
|
||||||
mount | grep vd
|
|
||||||
....
|
|
||||||
|
|
||||||
gives:
|
|
||||||
|
|
||||||
....
|
|
||||||
/dev/block/vda1 on /system type ext4 (ro,seclabel,relatime,data=ordered)
|
|
||||||
/dev/block/vde1 on /vendor type ext4 (ro,seclabel,relatime,data=ordered)
|
|
||||||
/dev/block/vdb on /cache type ext4 (rw,seclabel,nosuid,nodev,noatime,errors=panic,data=ordered)
|
|
||||||
....
|
|
||||||
|
|
||||||
and we see that the order of `vda`, `vdb`, etc. matches that in which `-drive` were given to QEMU.
|
|
||||||
|
|
||||||
Tested on: `8.1.0_r60`.
|
|
||||||
|
|
||||||
===== Android images read-only
|
|
||||||
|
|
||||||
From `mount`, we can see that some of the mounted images are `ro`.
|
|
||||||
|
|
||||||
Basically, every image that was given to QEMU as qcow2 is writable, and that qcow2 is an overlay over the actual original image.
|
|
||||||
|
|
||||||
In order to make `/system` and `/vendor` writable by using qcow2 for them as well, we must use the `-writable-system` option:
|
|
||||||
|
|
||||||
....
|
|
||||||
./run-android -- -writable-system
|
|
||||||
....
|
|
||||||
|
|
||||||
* https://android.stackexchange.com/questions/110927/how-to-mount-system-rewritable-or-read-only-rw-ro/207200#207200
|
|
||||||
* https://stackoverflow.com/questions/13089694/adb-remount-permission-denied-but-able-to-access-super-user-in-shell-android/43163693#43163693
|
|
||||||
|
|
||||||
then:
|
|
||||||
|
|
||||||
....
|
|
||||||
su
|
|
||||||
mount -o rw,remount /system
|
|
||||||
date >/system/a
|
|
||||||
....
|
|
||||||
|
|
||||||
Now reboot, and relaunch with `-writable-system` once again to pick up the modified qcow2 images:
|
|
||||||
|
|
||||||
....
|
|
||||||
./run-android -- -writable-system
|
|
||||||
....
|
|
||||||
|
|
||||||
and the newly created file is still there:
|
|
||||||
|
|
||||||
....
|
|
||||||
date >/system/a
|
|
||||||
....
|
|
||||||
|
|
||||||
`/system` and `/vendor` can be nuked quickly with:
|
|
||||||
|
|
||||||
....
|
|
||||||
./build-android --extra-args snod
|
|
||||||
./build-android --extra-args vnod
|
|
||||||
....
|
|
||||||
|
|
||||||
as mentioned at: https://stackoverflow.com/questions/29023406/how-to-just-build-android-system-image and on:
|
|
||||||
|
|
||||||
....
|
|
||||||
./build-android --extra-args help
|
|
||||||
....
|
|
||||||
|
|
||||||
Tested on: `8.1.0_r60`.
|
|
||||||
|
|
||||||
===== Android /data partition
|
|
||||||
|
|
||||||
When I install an app like F-Droid, it goes under `/data` according to:
|
|
||||||
|
|
||||||
....
|
|
||||||
find / -iname '*fdroid*'
|
|
||||||
....
|
|
||||||
|
|
||||||
and it <<disk-persistency,persists across boots>>.
|
|
||||||
|
|
||||||
`/data` is behind a RW LVM device:
|
|
||||||
|
|
||||||
....
|
|
||||||
/dev/block/dm-0 on /data type ext4 (rw,seclabel,nosuid,nodev,noatime,errors=panic,data=ordered)
|
|
||||||
....
|
|
||||||
|
|
||||||
but TODO I can't find where it comes from since I don't have the CLI tools mentioned at:
|
|
||||||
|
|
||||||
* https://superuser.com/questions/131519/what-is-this-dm-0-device
|
|
||||||
* https://unix.stackexchange.com/questions/185057/where-does-lvm-store-its-configuration
|
|
||||||
|
|
||||||
However, by looking at:
|
|
||||||
|
|
||||||
....
|
|
||||||
./run-android -- -help
|
|
||||||
....
|
|
||||||
|
|
||||||
we see:
|
|
||||||
|
|
||||||
....
|
|
||||||
-data <file> data image (default <datadir>/userdata-qemu.img
|
|
||||||
....
|
|
||||||
|
|
||||||
which confirms the suspicion that this data goes in `userdata-qemu.img`.
|
|
||||||
|
|
||||||
To reset images to their original state, just remove the qcow2 overlay and regenerate it: https://stackoverflow.com/questions/54446680/how-to-reset-the-userdata-image-when-building-android-aosp-and-running-it-on-the
|
|
||||||
|
|
||||||
Tested on: `8.1.0_r60`.
|
|
||||||
|
|
||||||
==== Install Android apps
|
|
||||||
|
|
||||||
I don't know how to download files from the web on Vanilla android, the default browser does not download anything, and there is no `wget`:
|
|
||||||
|
|
||||||
* https://android.stackexchange.com/questions/6984/how-to-download-files-from-the-web-in-the-android-browser
|
|
||||||
* https://stackoverflow.com/questions/26775079/wget-in-android-terminal
|
|
||||||
|
|
||||||
Installing with `adb install` does however work: https://stackoverflow.com/questions/7076240/install-an-apk-file-from-command-prompt
|
|
||||||
|
|
||||||
link:https://f-droid.org[F-Droid] installed fine like that, however it does not have permission to install apps: https://www.maketecheasier.com/install-apps-from-unknown-sources-android/
|
|
||||||
|
|
||||||
And the `Settings` app crashes so I can't change it, logcat contains:
|
|
||||||
|
|
||||||
....
|
|
||||||
No service published for: wifip2p
|
|
||||||
....
|
|
||||||
|
|
||||||
which is mentioned at: https://stackoverflow.com/questions/47839955/android-8-settings-app-crashes-on-emulator-with-clean-aosp-build
|
|
||||||
|
|
||||||
We also tried to enable it from the command line with:
|
|
||||||
|
|
||||||
....
|
|
||||||
settings put secure install_non_market_apps 1
|
|
||||||
....
|
|
||||||
|
|
||||||
as mentioned at: https://android.stackexchange.com/questions/77280/allow-unknown-sources-from-terminal-without-going-to-settings-app but it didn't work either.
|
|
||||||
|
|
||||||
No person alive seems to know how to pre-install apps on AOSP: https://stackoverflow.com/questions/6249458/pre-installing-android-application
|
|
||||||
|
|
||||||
Tested on: `8.1.0_r60`.
|
|
||||||
|
|
||||||
=== Android init
|
|
||||||
|
|
||||||
For Linux in general, see: <<init>>.
|
|
||||||
|
|
||||||
The `/init` executable interprets the `/init.rc` files, which is in a custom Android init system language: https://android.googlesource.com/platform/system/core/+/ee0e63f71d90537bb0570e77aa8a699cc222cfaf/init/README.md
|
|
||||||
|
|
||||||
The top of that file then sources other `.rc` files present on the root directory:
|
|
||||||
|
|
||||||
....
|
|
||||||
import /init.environ.rc
|
|
||||||
import /init.usb.rc
|
|
||||||
import /init.${ro.hardware}.rc
|
|
||||||
import /vendor/etc/init/hw/init.${ro.hardware}.rc
|
|
||||||
import /init.usb.configfs.rc
|
|
||||||
import /init.${ro.zygote}.rc
|
|
||||||
....
|
|
||||||
|
|
||||||
TODO: how is `ro.hardware` determined? https://stackoverflow.com/questions/20572781/android-boot-where-is-the-init-hardware-rc-read-in-init-c-where-are-servic It is a system property and can be obtained with:
|
|
||||||
|
|
||||||
....
|
|
||||||
getprop ro.hardware
|
|
||||||
....
|
|
||||||
|
|
||||||
This gives:
|
|
||||||
|
|
||||||
....
|
|
||||||
ranchu
|
|
||||||
....
|
|
||||||
|
|
||||||
which is the codename for the QEMU virtual platform we are running on: https://www.oreilly.com/library/view/android-system-programming/9781787125360/9736a97c-cd09-40c3-b14d-955717648302.xhtml
|
|
||||||
|
|
||||||
TODO: is it possible to add a custom `.rc` file without modifying the initrd that <<android-image-structure,gets mounted on root>>? https://stackoverflow.com/questions/9768103/make-persistent-changes-to-init-rc
|
|
||||||
|
|
||||||
Tested on: `8.1.0_r60`.
|
|
||||||
|
|
||||||
== About this repo
|
== About this repo
|
||||||
|
|
||||||
=== Supported hosts
|
=== Supported hosts
|
||||||
@@ -13057,88 +13187,6 @@ link:include/[] contains headers that are shared across both kernel modules and
|
|||||||
|
|
||||||
They contain data structs and magic constant for kernel to userland communication.
|
They contain data structs and magic constant for kernel to userland communication.
|
||||||
|
|
||||||
==== userland directory
|
|
||||||
|
|
||||||
Userland test programs. They can be used in the following ways:
|
|
||||||
|
|
||||||
* inside a full system simulation, e.g.: <<qemu-buildroot-setup>>
|
|
||||||
* inside <<user-mode-simulation>>
|
|
||||||
* directly on the host: <<userland-directory-host-build>>
|
|
||||||
|
|
||||||
For usage inside full system simulation, first ensure that Buildroot has been built for the toolchain, and then build the examples with:
|
|
||||||
|
|
||||||
....
|
|
||||||
./build-userland
|
|
||||||
....
|
|
||||||
|
|
||||||
Source: link:build-userland[].
|
|
||||||
|
|
||||||
This makes them visible immediately on the <<9p>> mount of a running simulator.
|
|
||||||
|
|
||||||
In order to place them in the root filesystem image itself, you must also run:
|
|
||||||
|
|
||||||
....
|
|
||||||
./build-buildroot
|
|
||||||
....
|
|
||||||
|
|
||||||
===== userland directory host build
|
|
||||||
|
|
||||||
It is possible to build and run some of the userland examples directly on your host:
|
|
||||||
|
|
||||||
....
|
|
||||||
cd userland
|
|
||||||
make
|
|
||||||
./hello.out
|
|
||||||
make clean
|
|
||||||
....
|
|
||||||
|
|
||||||
or more cleanly out of tree:
|
|
||||||
|
|
||||||
....
|
|
||||||
./build-userland --gcc-which host --userland-build-id host
|
|
||||||
"$(./getvar --userland-build-id host userland_build_dir)/hello.out"
|
|
||||||
....
|
|
||||||
|
|
||||||
Extra make flags may be passed as:
|
|
||||||
|
|
||||||
....
|
|
||||||
./build-userland --gcc-which host --userland-build-id host-static --make-args='-B CFLAGS_EXTRA=-static'
|
|
||||||
"$(./getvar --userland-build-id host-static userland_build_dir)/hello.out"
|
|
||||||
....
|
|
||||||
|
|
||||||
This for example would both force a rebuild due to `-B` and link statically due to `CFLAGS_EXTRA=-static`.
|
|
||||||
|
|
||||||
TODO: OpenMP does not like `-static`:
|
|
||||||
|
|
||||||
....
|
|
||||||
/usr/lib/gcc/x86_64-linux-gnu/5/libgomp.a(target.o): In function `gomp_target_init':
|
|
||||||
(.text+0xba): warning: Using 'dlopen' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
|
|
||||||
....
|
|
||||||
|
|
||||||
See: https://stackoverflow.com/questions/23869981/linking-openmp-statically-with-gcc
|
|
||||||
|
|
||||||
===== userland cheats
|
|
||||||
|
|
||||||
We have accumulated considerable material in the following userland subjects.
|
|
||||||
|
|
||||||
====== C
|
|
||||||
|
|
||||||
Programs under link:userland/c/[] are examples of link:https://en.wikipedia.org/wiki/ANSI_C[ANSI C] programming.
|
|
||||||
|
|
||||||
[[cpp]]
|
|
||||||
====== C++
|
|
||||||
|
|
||||||
Programs under link:userland/cpp/[] are examples of link:https://en.wikipedia.org/wiki/C%2B%2B#Standardization[ISO C] programming.
|
|
||||||
|
|
||||||
====== POSIX
|
|
||||||
|
|
||||||
Programs under link:userland/posix/[] are examples of POSIX C programming.
|
|
||||||
|
|
||||||
What is POSIX:
|
|
||||||
|
|
||||||
* https://stackoverflow.com/questions/1780599/what-is-the-meaning-of-posix/31865755#31865755
|
|
||||||
* https://unix.stackexchange.com/questions/11983/what-exactly-is-posix/220877#220877
|
|
||||||
|
|
||||||
==== buildroot_packages directory
|
==== buildroot_packages directory
|
||||||
|
|
||||||
Source: link:buildroot_packages/[]
|
Source: link:buildroot_packages/[]
|
||||||
@@ -13171,7 +13219,7 @@ You can force a rebuild with:
|
|||||||
./build-buildroot --config 'BR2_PACKAGE_SAMPLE_PACKAGE=y' -- sample_package-reconfigure
|
./build-buildroot --config 'BR2_PACKAGE_SAMPLE_PACKAGE=y' -- sample_package-reconfigure
|
||||||
....
|
....
|
||||||
|
|
||||||
Buildroot packages are convenient, but in general, if a package if very important to you, but not really mergeable back to Buildroot, you might want to just use a custom build script for it, and point it to the Buildroot toolchain, and then use `BR2_ROOTFS_OVERLAY`, much like we do for <<userland-directory>>.
|
Buildroot packages are convenient, but in general, if a package if very important to you, but not really mergeable back to Buildroot, you might want to just use a custom build script for it, and point it to the Buildroot toolchain, and then use `BR2_ROOTFS_OVERLAY`, much like we do for <<userland-setup>>.
|
||||||
|
|
||||||
A custom build script can give you more flexibility: e.g. the package can be made work with other root filesystems more easily, have better <<9p>> support, and rebuild faster as it evades some Buildroot boilerplate.
|
A custom build script can give you more flexibility: e.g. the package can be made work with other root filesystems more easily, have better <<9p>> support, and rebuild faster as it evades some Buildroot boilerplate.
|
||||||
|
|
||||||
|
|||||||
375
build-userland
375
build-userland
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
import common
|
|
||||||
import threading
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from shell_helpers import LF
|
import threading
|
||||||
|
|
||||||
error = False
|
from shell_helpers import LF
|
||||||
|
import common
|
||||||
|
from thread_pool import ThreadPool
|
||||||
|
|
||||||
class Main(common.BuildCliFunction):
|
class Main(common.BuildCliFunction):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -32,10 +31,15 @@ allows us to build examples that rely on it.
|
|||||||
'--in-tree',
|
'--in-tree',
|
||||||
default=False,
|
default=False,
|
||||||
help='''\
|
help='''\
|
||||||
Magic build mode tailored to build from within the source tree:
|
Place build output inside soure tree to conveniently run it, especially when
|
||||||
|
building with the host toolchain.
|
||||||
* place build output inside soure tree to conveniently run it
|
''',
|
||||||
* if not targets are given, build use the current working directory
|
)
|
||||||
|
self.add_argument(
|
||||||
|
'--target-cwd',
|
||||||
|
default=False,
|
||||||
|
help='''\
|
||||||
|
Treat targets as relative to the current working directory.
|
||||||
''',
|
''',
|
||||||
)
|
)
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
@@ -63,85 +67,76 @@ Default: build all examples that have their package dependencies met, e.g.:
|
|||||||
extra_deps=None,
|
extra_deps=None,
|
||||||
extra_objs=None,
|
extra_objs=None,
|
||||||
link=True,
|
link=True,
|
||||||
raise_on_failure=True,
|
|
||||||
thread_limiter=None,
|
|
||||||
):
|
):
|
||||||
try:
|
if extra_deps is None:
|
||||||
if extra_deps is None:
|
extra_deps = []
|
||||||
extra_deps = []
|
if extra_objs is None:
|
||||||
if extra_objs is None:
|
extra_objs = []
|
||||||
extra_objs = []
|
if ccflags_after is None:
|
||||||
if ccflags_after is None:
|
ccflags_after = []
|
||||||
ccflags_after = []
|
ret = 0
|
||||||
ret = 0
|
if self.need_rebuild([in_path] + extra_objs + extra_deps, out_path):
|
||||||
if self.need_rebuild([in_path] + extra_objs + extra_deps, out_path):
|
ccflags = ccflags.copy()
|
||||||
ccflags = ccflags.copy()
|
if not link:
|
||||||
if not link:
|
ccflags.extend(['-c', LF])
|
||||||
ccflags.extend(['-c', LF])
|
in_ext = os.path.splitext(in_path)[1]
|
||||||
in_ext = os.path.splitext(in_path)[1]
|
do_compile = True
|
||||||
do_compile = True
|
if in_ext in (self.env['c_ext'], self.env['asm_ext']):
|
||||||
if in_ext == self.env['c_ext']:
|
cc = self.env['gcc']
|
||||||
cc = self.env['gcc']
|
if cstd is None:
|
||||||
if cstd is None:
|
std = self.default_cstd
|
||||||
std = self.default_cstd
|
|
||||||
else:
|
|
||||||
std = cstd
|
|
||||||
ccflags.extend([
|
|
||||||
'-fopenmp', LF,
|
|
||||||
])
|
|
||||||
elif in_ext == self.env['cxx_ext']:
|
|
||||||
cc = self.env['gxx']
|
|
||||||
if cxxstd is None:
|
|
||||||
std = self.default_cxxstd
|
|
||||||
else:
|
|
||||||
std = cxxstd
|
|
||||||
else:
|
else:
|
||||||
do_compile = False
|
std = cstd
|
||||||
if do_compile:
|
ccflags.extend([
|
||||||
ret = self.sh.run_cmd(
|
'-fopenmp', LF,
|
||||||
(
|
])
|
||||||
[
|
elif in_ext == self.env['cxx_ext']:
|
||||||
cc, LF,
|
cc = self.env['gxx']
|
||||||
] +
|
if cxxstd is None:
|
||||||
ccflags +
|
std = self.default_cxxstd
|
||||||
[
|
else:
|
||||||
'-std={}'.format(std), LF,
|
std = cxxstd
|
||||||
'-o', out_path, LF,
|
else:
|
||||||
in_path, LF,
|
do_compile = False
|
||||||
] +
|
if do_compile:
|
||||||
self.sh.add_newlines(extra_objs) +
|
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||||
[
|
ret = self.sh.run_cmd(
|
||||||
'-lm', LF,
|
(
|
||||||
'-pthread', LF,
|
[
|
||||||
] +
|
cc, LF,
|
||||||
ccflags_after
|
] +
|
||||||
),
|
ccflags +
|
||||||
extra_paths=[self.env['ccache_dir']],
|
[
|
||||||
raise_on_failure=raise_on_failure,
|
'-std={}'.format(std), LF,
|
||||||
)
|
'-o', out_path, LF,
|
||||||
finally:
|
in_path, LF,
|
||||||
if thread_limiter is not None:
|
] +
|
||||||
thread_limiter.release()
|
self.sh.add_newlines(extra_objs) +
|
||||||
if ret != 0:
|
[
|
||||||
self.error = True
|
'-lm', LF,
|
||||||
|
'-pthread', LF,
|
||||||
|
] +
|
||||||
|
ccflags_after
|
||||||
|
),
|
||||||
|
extra_paths=[self.env['ccache_dir']],
|
||||||
|
)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _get_targets(self):
|
def _get_targets(self):
|
||||||
if self.env['_args_given']['targets']:
|
if self.env['_args_given']['targets']:
|
||||||
targets = self.env['targets']
|
targets = self.env['targets']
|
||||||
if self.env['in_tree']:
|
if self.env['target_cwd']:
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
targets = [os.path.join(cwd, target) for target in targets]
|
targets = [os.path.join(cwd, target) for target in targets]
|
||||||
return targets
|
return targets
|
||||||
else:
|
else:
|
||||||
if self.env['in_tree']:
|
if self.env['target_cwd']:
|
||||||
return [os.getcwd()]
|
return [os.getcwd()]
|
||||||
else:
|
else:
|
||||||
return [self.env['userland_source_dir']]
|
return [self.env['userland_source_dir']]
|
||||||
|
|
||||||
def build(self):
|
def build(self):
|
||||||
build_dir = self.get_build_dir()
|
build_dir = self.get_build_dir()
|
||||||
os.makedirs(build_dir, exist_ok=True)
|
|
||||||
has_packages = set(self.env['has_package'])
|
has_packages = set(self.env['has_package'])
|
||||||
ccflags = [
|
ccflags = [
|
||||||
'-I', self.env['root_dir'], LF,
|
'-I', self.env['root_dir'], LF,
|
||||||
@@ -166,6 +161,25 @@ Default: build all examples that have their package dependencies met, e.g.:
|
|||||||
extra_deps=[self.env['common_h']],
|
extra_deps=[self.env['common_h']],
|
||||||
link=False,
|
link=False,
|
||||||
)
|
)
|
||||||
|
common_obj_asm = os.path.join(
|
||||||
|
build_dir,
|
||||||
|
'arch',
|
||||||
|
'main' + self.env['obj_ext']
|
||||||
|
)
|
||||||
|
common_obj_asm_relpath = os.path.join(
|
||||||
|
'arch',
|
||||||
|
'main' + self.env['c_ext']
|
||||||
|
)
|
||||||
|
self._build_one(
|
||||||
|
in_path=os.path.join(
|
||||||
|
self.env['userland_source_dir'],
|
||||||
|
common_obj_asm_relpath
|
||||||
|
),
|
||||||
|
out_path=common_obj_asm,
|
||||||
|
ccflags=ccflags,
|
||||||
|
extra_deps=[self.env['common_h']],
|
||||||
|
link=False,
|
||||||
|
)
|
||||||
pkgs = {
|
pkgs = {
|
||||||
'eigen': {
|
'eigen': {
|
||||||
# TODO: was failing with:
|
# TODO: was failing with:
|
||||||
@@ -189,84 +203,136 @@ Default: build all examples that have their package dependencies met, e.g.:
|
|||||||
'openblas': {},
|
'openblas': {},
|
||||||
}
|
}
|
||||||
rootdir_abs_len = len(self.env['userland_source_dir'])
|
rootdir_abs_len = len(self.env['userland_source_dir'])
|
||||||
thread_limiter = threading.BoundedSemaphore(self.env['nproc'])
|
thread_pool = ThreadPool(
|
||||||
self.error = False
|
self._build_one,
|
||||||
for target in self._get_targets():
|
nthreads=self.env['nproc'],
|
||||||
target = self.resolve_userland_source(target)
|
)
|
||||||
for path, in_dirnames, in_filenames in self.sh.walk(target):
|
class ExitLoop(Exception): pass
|
||||||
in_dirnames.sort()
|
try:
|
||||||
path_abs = os.path.abspath(path)
|
for target in self._get_targets():
|
||||||
dirpath_relative_root = path_abs[rootdir_abs_len + 1:]
|
target = self.resolve_userland_source(target)
|
||||||
dirpath_relative_root_components = dirpath_relative_root.split(os.sep)
|
for path, in_dirnames, in_filenames in self.sh.walk(target):
|
||||||
if (
|
in_dirnames.sort()
|
||||||
len(dirpath_relative_root_components) < 2 or
|
in_filenames.sort()
|
||||||
dirpath_relative_root_components[0] != 'arch' or
|
path_abs = os.path.abspath(path)
|
||||||
dirpath_relative_root_components[1] == self.env['arch']
|
dirpath_relative_root = path_abs[rootdir_abs_len + 1:]
|
||||||
):
|
dirpath_relative_root_components = dirpath_relative_root.split(os.sep)
|
||||||
out_dir = os.path.join(
|
dirpath_relative_root_components_len = len(dirpath_relative_root_components)
|
||||||
build_dir,
|
do_build_dir = True
|
||||||
dirpath_relative_root
|
in_arch = False
|
||||||
)
|
if dirpath_relative_root_components_len > 0:
|
||||||
os.makedirs(out_dir, exist_ok=True)
|
if dirpath_relative_root_components[0] == 'arch':
|
||||||
ccflags_dir = ccflags.copy()
|
if dirpath_relative_root_components_len > 1:
|
||||||
if dirpath_relative_root_components == ['gcc']:
|
if dirpath_relative_root_components[1] == self.env['arch']:
|
||||||
cstd = 'gnu11'
|
in_arch = True
|
||||||
cxxstd = 'gnu++17'
|
else:
|
||||||
else:
|
do_build_dir = False
|
||||||
cstd = self.default_cstd
|
else:
|
||||||
cxxstd = self.default_cxxstd
|
do_build_dir = False
|
||||||
# -pedantic complains even if we use -std=gnu11.
|
if do_build_dir:
|
||||||
ccflags_dir.extend(['-pedantic', LF])
|
out_dir = os.path.join(
|
||||||
for in_filename in in_filenames:
|
build_dir,
|
||||||
in_path = os.path.join(path, in_filename)
|
dirpath_relative_root
|
||||||
in_name, in_ext = os.path.splitext(in_filename)
|
|
||||||
out_path = os.path.join(
|
|
||||||
out_dir,
|
|
||||||
in_name + self.env['userland_build_ext']
|
|
||||||
)
|
)
|
||||||
pkg_key = in_name.split('_')[0]
|
common_objs_dir = [common_obj]
|
||||||
ccflags_file = ccflags_dir.copy()
|
ccflags_dir = ccflags.copy()
|
||||||
ccflags_after = []
|
if dirpath_relative_root_components == ['gcc']:
|
||||||
if pkg_key in pkgs:
|
cstd = 'gnu11'
|
||||||
if pkg_key not in has_packages:
|
cxxstd = 'gnu++17'
|
||||||
|
else:
|
||||||
|
cstd = self.default_cstd
|
||||||
|
cxxstd = self.default_cxxstd
|
||||||
|
# -pedantic complains even if we use -std=gnu11.
|
||||||
|
ccflags_dir.extend(['-pedantic', LF])
|
||||||
|
if in_arch:
|
||||||
|
ccflags_dir.extend([
|
||||||
|
'-I', os.path.join(self.env['userland_source_arch_arch_dir']), LF,
|
||||||
|
'-I', os.path.join(self.env['userland_source_arch_dir']), LF,
|
||||||
|
'-fno-pie', LF,
|
||||||
|
'-no-pie', LF,
|
||||||
|
])
|
||||||
|
if 'freestanding' in dirpath_relative_root_components:
|
||||||
|
common_objs_dir = []
|
||||||
|
ccflags_dir.extend([
|
||||||
|
'-ffreestanding', LF,
|
||||||
|
'-nostdlib', LF,
|
||||||
|
'-static', LF,
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
if 'c' in dirpath_relative_root_components:
|
||||||
|
common_objs_dir = []
|
||||||
|
else:
|
||||||
|
common_objs_dir = [common_obj_asm]
|
||||||
|
if self.env['arch'] == 'arm':
|
||||||
|
ccflags_dir.extend([
|
||||||
|
'-Xassembler', '-mcpu=cortex-a72', LF,
|
||||||
|
# To prevent:
|
||||||
|
# > vfp.S: Error: selected processor does not support <FPU instruction> in ARM mode
|
||||||
|
# https://stackoverflow.com/questions/41131432/cross-compiling-error-selected-processor-does-not-support-fmrx-r3-fpexc-in/52875732#52875732
|
||||||
|
# We aim to take the most extended mode currently available that works on QEMU.
|
||||||
|
'-Xassembler', '-mfpu=crypto-neon-fp-armv8.1', LF,
|
||||||
|
'-Xassembler', '-meabi=5', LF,
|
||||||
|
# Treat inline assembly as arm instead of thumb
|
||||||
|
# The opposite of -mthumb.
|
||||||
|
'-marm', LF,
|
||||||
|
# Make gcc generate .syntax unified for inline assembly.
|
||||||
|
# However, it gets ignored if -marm is given, which a GCC bug that was recently fixed:
|
||||||
|
# https://stackoverflow.com/questions/54078112/how-to-write-syntax-unified-ual-armv7-inline-assembly-in-gcc/54132097#54132097
|
||||||
|
# So we just write divided inline assembly for now.
|
||||||
|
'-masm-syntax-unified', LF,
|
||||||
|
])
|
||||||
|
for in_filename in in_filenames:
|
||||||
|
path_relative_root = os.path.join(dirpath_relative_root, in_filename)
|
||||||
|
if path_relative_root == common_obj_asm_relpath:
|
||||||
continue
|
continue
|
||||||
pkg = pkgs[pkg_key]
|
in_path = os.path.join(path, in_filename)
|
||||||
if 'ccflags' in pkg:
|
in_name, in_ext = os.path.splitext(in_filename)
|
||||||
ccflags_file.extend(pkg['ccflags'])
|
out_path = os.path.join(
|
||||||
else:
|
out_dir,
|
||||||
pkg_config_output = subprocess.check_output([
|
in_name + self.env['userland_build_ext']
|
||||||
self.env['buildroot_pkg_config'],
|
)
|
||||||
'--cflags',
|
pkg_key = in_name.split('_')[0]
|
||||||
pkg_key
|
ccflags_file = ccflags_dir.copy()
|
||||||
]).decode()
|
ccflags_after = []
|
||||||
ccflags_file.extend(self.sh.shlex_split(pkg_config_output))
|
if pkg_key in pkgs:
|
||||||
if 'ccflags_after' in pkg:
|
if pkg_key not in has_packages:
|
||||||
ccflags_file.extend(pkg['ccflags_after'])
|
continue
|
||||||
else:
|
pkg = pkgs[pkg_key]
|
||||||
pkg_config_output = subprocess.check_output([
|
if 'ccflags' in pkg:
|
||||||
self.env['buildroot_pkg_config'],
|
ccflags_file.extend(pkg['ccflags'])
|
||||||
'--libs',
|
else:
|
||||||
pkg_key
|
pkg_config_output = subprocess.check_output([
|
||||||
]).decode()
|
self.env['buildroot_pkg_config'],
|
||||||
ccflags_after.extend(self.sh.shlex_split(pkg_config_output))
|
'--cflags',
|
||||||
thread_limiter.acquire()
|
pkg_key
|
||||||
if self.error:
|
]).decode()
|
||||||
return 1
|
ccflags_file.extend(self.sh.shlex_split(pkg_config_output))
|
||||||
thread = threading.Thread(
|
if 'ccflags_after' in pkg:
|
||||||
target=self._build_one,
|
ccflags_file.extend(pkg['ccflags_after'])
|
||||||
kwargs={
|
else:
|
||||||
'in_path': in_path,
|
pkg_config_output = subprocess.check_output([
|
||||||
'out_path': out_path,
|
self.env['buildroot_pkg_config'],
|
||||||
'ccflags': ccflags_file,
|
'--libs',
|
||||||
'cstd': cstd,
|
pkg_key
|
||||||
'cxxstd': cxxstd,
|
]).decode()
|
||||||
'extra_objs': [common_obj],
|
ccflags_after.extend(self.sh.shlex_split(pkg_config_output))
|
||||||
'ccflags_after': ccflags_after,
|
error = thread_pool.submit({
|
||||||
'raise_on_failure': False,
|
'in_path': in_path,
|
||||||
'thread_limiter': thread_limiter,
|
'out_path': out_path,
|
||||||
}
|
'ccflags': ccflags_file,
|
||||||
)
|
'cstd': cstd,
|
||||||
thread.start()
|
'cxxstd': cxxstd,
|
||||||
|
'extra_objs': common_objs_dir,
|
||||||
|
'ccflags_after': ccflags_after,
|
||||||
|
})
|
||||||
|
if error is not None:
|
||||||
|
raise ExitLoop()
|
||||||
|
except ExitLoop:
|
||||||
|
pass
|
||||||
|
error = thread_pool.join()
|
||||||
|
if error is not None:
|
||||||
|
print(error)
|
||||||
|
return 1
|
||||||
self.sh.copy_dir_if_update(
|
self.sh.copy_dir_if_update(
|
||||||
srcdir=build_dir,
|
srcdir=build_dir,
|
||||||
destdir=self.env['out_rootfs_overlay_dir'],
|
destdir=self.env['out_rootfs_overlay_dir'],
|
||||||
@@ -277,12 +343,15 @@ Default: build all examples that have their package dependencies met, e.g.:
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
if self.env['in_tree']:
|
if self.env['in_tree']:
|
||||||
for target in self._get_targets():
|
for target in self._get_targets():
|
||||||
for path, dirnames, filenames in os.walk(target):
|
if os.path.exists(target):
|
||||||
filenames.sort()
|
for path, dirnames, filenames in os.walk(target):
|
||||||
dirnames.sort()
|
filenames.sort()
|
||||||
for filename in filenames:
|
dirnames.sort()
|
||||||
if os.path.splitext(filename)[1] in self.env['userland_out_exts']:
|
for filename in filenames:
|
||||||
self.sh.rmrf(os.path.join(path, filename))
|
if os.path.splitext(filename)[1] in self.env['userland_out_exts']:
|
||||||
|
self.sh.rmrf(os.path.join(path, filename))
|
||||||
|
else:
|
||||||
|
raise Exception('Path does not exist: ' + target)
|
||||||
else:
|
else:
|
||||||
self.sh.rmrf(self.get_build_dir())
|
self.sh.rmrf(self.get_build_dir())
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
"$(git rev-parse --show-toplevel)/build-userland" --gcc-which host --in-tree "$@"
|
"$(git rev-parse --show-toplevel)/build-userland" \
|
||||||
|
--gcc-which host \
|
||||||
|
--in-tree \
|
||||||
|
--target-cwd \
|
||||||
|
"$@" \
|
||||||
|
;
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ consts['kernel_modules_subdir'] = 'kernel_modules'
|
|||||||
consts['kernel_modules_source_dir'] = os.path.join(consts['root_dir'], consts['kernel_modules_subdir'])
|
consts['kernel_modules_source_dir'] = os.path.join(consts['root_dir'], consts['kernel_modules_subdir'])
|
||||||
consts['userland_subdir'] = 'userland'
|
consts['userland_subdir'] = 'userland'
|
||||||
consts['userland_source_dir'] = os.path.join(consts['root_dir'], consts['userland_subdir'])
|
consts['userland_source_dir'] = os.path.join(consts['root_dir'], consts['userland_subdir'])
|
||||||
|
consts['userland_source_arch_dir'] = os.path.join(consts['userland_source_dir'], 'arch')
|
||||||
consts['userland_build_ext'] = '.out'
|
consts['userland_build_ext'] = '.out'
|
||||||
consts['include_subdir'] = 'include'
|
consts['include_subdir'] = 'include'
|
||||||
consts['include_source_dir'] = os.path.join(consts['root_dir'], consts['include_subdir'])
|
consts['include_source_dir'] = os.path.join(consts['root_dir'], consts['include_subdir'])
|
||||||
@@ -714,12 +715,15 @@ Valid emulators: {}
|
|||||||
env['initarg'] = 'init'
|
env['initarg'] = 'init'
|
||||||
env['quit_init'] = '{}={}'.format(env['initarg'], env['userland_quit_cmd'])
|
env['quit_init'] = '{}={}'.format(env['initarg'], env['userland_quit_cmd'])
|
||||||
|
|
||||||
|
# Userland
|
||||||
|
env['userland_source_arch_arch_dir'] = join(env['userland_source_arch_dir'], env['arch'])
|
||||||
|
env['userland_build_dir'] = join(env['out_dir'], 'userland', env['userland_build_id'], env['arch'])
|
||||||
|
|
||||||
# Kernel modules.
|
# Kernel modules.
|
||||||
env['kernel_modules_build_dir'] = join(env['kernel_modules_build_base_dir'], env['arch'])
|
env['kernel_modules_build_dir'] = join(env['kernel_modules_build_base_dir'], env['arch'])
|
||||||
env['kernel_modules_build_subdir'] = join(env['kernel_modules_build_dir'], env['kernel_modules_subdir'])
|
env['kernel_modules_build_subdir'] = join(env['kernel_modules_build_dir'], env['kernel_modules_subdir'])
|
||||||
env['kernel_modules_build_host_dir'] = join(env['kernel_modules_build_base_dir'], 'host')
|
env['kernel_modules_build_host_dir'] = join(env['kernel_modules_build_base_dir'], 'host')
|
||||||
env['kernel_modules_build_host_subdir'] = join(env['kernel_modules_build_host_dir'], env['kernel_modules_subdir'])
|
env['kernel_modules_build_host_subdir'] = join(env['kernel_modules_build_host_dir'], env['kernel_modules_subdir'])
|
||||||
env['userland_build_dir'] = join(env['out_dir'], 'userland', env['userland_build_id'], env['arch'])
|
|
||||||
env['out_rootfs_overlay_dir'] = join(env['out_dir'], 'rootfs_overlay', env['arch'])
|
env['out_rootfs_overlay_dir'] = join(env['out_dir'], 'rootfs_overlay', env['arch'])
|
||||||
env['out_rootfs_overlay_bin_dir'] = join(env['out_rootfs_overlay_dir'], 'bin')
|
env['out_rootfs_overlay_bin_dir'] = join(env['out_rootfs_overlay_dir'], 'bin')
|
||||||
|
|
||||||
|
|||||||
@@ -126,12 +126,12 @@ class ShellHelpers:
|
|||||||
src = os.path.join(srcdir, basename)
|
src = os.path.join(srcdir, basename)
|
||||||
if os.path.isfile(src):
|
if os.path.isfile(src):
|
||||||
noext, ext = os.path.splitext(basename)
|
noext, ext = os.path.splitext(basename)
|
||||||
if filter_ext is not None and ext == filter_ext:
|
dest = os.path.join(destdir, basename)
|
||||||
distutils.file_util.copy_file(
|
if (
|
||||||
src,
|
(filter_ext is not None and ext == filter_ext) and
|
||||||
os.path.join(destdir, basename),
|
(os.path.exists(dest) and os.path.getmtime(src) > os.path.getmtime(dest))
|
||||||
update=1,
|
):
|
||||||
)
|
self.cp(src, dest)
|
||||||
|
|
||||||
def copy_dir_if_update(self, srcdir, destdir, filter_ext=None):
|
def copy_dir_if_update(self, srcdir, destdir, filter_ext=None):
|
||||||
self.copy_dir_if_update_non_recursive(srcdir, destdir, filter_ext)
|
self.copy_dir_if_update_non_recursive(srcdir, destdir, filter_ext)
|
||||||
@@ -283,7 +283,9 @@ class ShellHelpers:
|
|||||||
#signal.signal(signal.SIGPIPE, sigpipe_old)
|
#signal.signal(signal.SIGPIPE, sigpipe_old)
|
||||||
returncode = proc.returncode
|
returncode = proc.returncode
|
||||||
if returncode != 0 and raise_on_failure:
|
if returncode != 0 and raise_on_failure:
|
||||||
raise Exception('Command exited with status: {}'.format(returncode))
|
e = Exception('Command exited with status: {}'.format(returncode))
|
||||||
|
e.returncode = returncode
|
||||||
|
raise e
|
||||||
return returncode
|
return returncode
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
221
thread_pool.py
Normal file
221
thread_pool.py
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from typing import Any, Callable, Dict, Iterable, Union
|
||||||
|
import multiprocessing
|
||||||
|
import queue
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
class ThreadPool:
|
||||||
|
'''
|
||||||
|
Start a pool of a limited number of threads to do some work.
|
||||||
|
|
||||||
|
This is similar to the stdlib concurrent, but I could not find
|
||||||
|
how to reach all my design goals with that implementation:
|
||||||
|
|
||||||
|
- the input function does not need to be modified
|
||||||
|
- limit the number of threads
|
||||||
|
- queue sizes closely follow number of threads
|
||||||
|
- if an exception happens, optionally stop soon afterwards
|
||||||
|
|
||||||
|
Functional form and further discussion at:
|
||||||
|
https://stackoverflow.com/questions/19369724/the-right-way-to-limit-maximum-number-of-threads-running-at-once/55263676#55263676
|
||||||
|
|
||||||
|
This class form allows to use your own while loops with submit().
|
||||||
|
|
||||||
|
Quick test with:
|
||||||
|
|
||||||
|
./thread_limit.py 2 -10 20 0
|
||||||
|
./thread_limit.py 2 -10 20 1
|
||||||
|
./thread_limit.py 2 -10 20 2
|
||||||
|
./thread_limit.py 2 -10 20 3
|
||||||
|
|
||||||
|
These ensure that execution stops neatly on error.
|
||||||
|
'''
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
func: Callable,
|
||||||
|
handle_output: Union[Callable[[Any,Any,Exception],Any],None] = None,
|
||||||
|
nthreads: Union[int,None] = None
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
Start in a thread pool immediately.
|
||||||
|
|
||||||
|
join() must be called afterwards at some point.
|
||||||
|
|
||||||
|
:param func: main work function to be evaluated.
|
||||||
|
:param handle_output: called on func return values as they
|
||||||
|
are returned.
|
||||||
|
|
||||||
|
Signature is: handle_output(input, output, exception) where:
|
||||||
|
|
||||||
|
- input: input given to func
|
||||||
|
- output: return value of func
|
||||||
|
- exception: the exception that func raised, or None otherwise
|
||||||
|
|
||||||
|
If this function returns non-None or raises, stop feeding
|
||||||
|
new input and exit ASAP when all currently running threads
|
||||||
|
have finished.
|
||||||
|
|
||||||
|
Default: a handler that does nothing and just exits on exception.
|
||||||
|
:param nthreads: number of threads to use. Default: nproc.
|
||||||
|
'''
|
||||||
|
self.func = func
|
||||||
|
if handle_output is None:
|
||||||
|
handle_output = lambda input, output, exception: exception
|
||||||
|
self.handle_output = handle_output
|
||||||
|
if nthreads is None:
|
||||||
|
nthreads = multiprocessing.cpu_count()
|
||||||
|
self.nthreads = nthreads
|
||||||
|
self.error_output = None
|
||||||
|
self.error_output_lock = threading.Lock()
|
||||||
|
self.in_queue = queue.Queue(maxsize=nthreads)
|
||||||
|
self.threads = []
|
||||||
|
for i in range(self.nthreads):
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=self._func_runner,
|
||||||
|
)
|
||||||
|
self.threads.append(thread)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def submit(self, work):
|
||||||
|
'''
|
||||||
|
Submit work. Block if there is already enough work scheduled (~nthreads).
|
||||||
|
|
||||||
|
:return: if an error occurred in some previously executed thread, the error.
|
||||||
|
Otherwise, None. This allows the caller to stop submitting further
|
||||||
|
work if desired.
|
||||||
|
'''
|
||||||
|
self.in_queue.put(work)
|
||||||
|
return self.error_output
|
||||||
|
|
||||||
|
def join(self):
|
||||||
|
'''
|
||||||
|
Request all threads to stop after they finish currently submitted work.
|
||||||
|
|
||||||
|
:return: same as submit()
|
||||||
|
'''
|
||||||
|
for thread in range(self.nthreads):
|
||||||
|
self.in_queue.put(None)
|
||||||
|
for thread in self.threads:
|
||||||
|
thread.join()
|
||||||
|
return self.error_output
|
||||||
|
|
||||||
|
def _func_runner(self):
|
||||||
|
while True:
|
||||||
|
work = self.in_queue.get(block=True)
|
||||||
|
if work is None:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
exception = None
|
||||||
|
out = self.func(**work)
|
||||||
|
except Exception as e:
|
||||||
|
exception = e
|
||||||
|
try:
|
||||||
|
handle_output_return = self.handle_output(work, out, exception)
|
||||||
|
except Exception as e:
|
||||||
|
self.error_output_lock.acquire()
|
||||||
|
self.error_output = (work, out, e)
|
||||||
|
self.error_output_lock.release()
|
||||||
|
else:
|
||||||
|
if handle_output_return is not None:
|
||||||
|
self.error_output_lock.acquire()
|
||||||
|
self.error_output = handle_output_return
|
||||||
|
self.error_output_lock.release()
|
||||||
|
finally:
|
||||||
|
self.in_queue.task_done()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
def my_func(i):
|
||||||
|
'''
|
||||||
|
The main function that will be evaluated.
|
||||||
|
|
||||||
|
It sleeps to simulate an IO operation.
|
||||||
|
'''
|
||||||
|
time.sleep((abs(i) % 4) / 10.0)
|
||||||
|
return 10.0 / i
|
||||||
|
|
||||||
|
def get_work(min_, max_):
|
||||||
|
'''
|
||||||
|
Generate simple range work for my_func.
|
||||||
|
'''
|
||||||
|
for i in range(min_, max_):
|
||||||
|
yield {'i': i}
|
||||||
|
|
||||||
|
def handle_output_print(input, output, exception):
|
||||||
|
'''
|
||||||
|
Print outputs and exit immediately on failure.
|
||||||
|
'''
|
||||||
|
print('{!r} {!r} {!r}'.format(input, output, exception))
|
||||||
|
return exception
|
||||||
|
|
||||||
|
def handle_output_print_no_exit(input, output, exception):
|
||||||
|
'''
|
||||||
|
Print outputs, don't exit on failure.
|
||||||
|
'''
|
||||||
|
print('{!r} {!r} {!r}'.format(input, output, exception))
|
||||||
|
|
||||||
|
out_queue = queue.Queue()
|
||||||
|
def handle_output_queue(input, output, exception):
|
||||||
|
'''
|
||||||
|
Store outputs in a queue for later usage.
|
||||||
|
'''
|
||||||
|
global out_queue
|
||||||
|
out_queue.put((input, output, exception))
|
||||||
|
return exception
|
||||||
|
|
||||||
|
def handle_output_raise(input, output, exception):
|
||||||
|
'''
|
||||||
|
Raise if input == 10, to test that execution
|
||||||
|
stops nicely if this raises.
|
||||||
|
'''
|
||||||
|
print('{!r} {!r} {!r}'.format(input, output, exception))
|
||||||
|
if input['i'] == 10:
|
||||||
|
raise Exception
|
||||||
|
|
||||||
|
# CLI arguments.
|
||||||
|
argv_len = len(sys.argv)
|
||||||
|
if argv_len > 1:
|
||||||
|
nthreads = int(sys.argv[1])
|
||||||
|
if nthreads == 0:
|
||||||
|
nthreads = None
|
||||||
|
else:
|
||||||
|
nthreads = None
|
||||||
|
if argv_len > 2:
|
||||||
|
min_ = int(sys.argv[2])
|
||||||
|
else:
|
||||||
|
min_ = 1
|
||||||
|
if argv_len > 3:
|
||||||
|
max_ = int(sys.argv[3])
|
||||||
|
else:
|
||||||
|
max_ = 100
|
||||||
|
if argv_len > 4:
|
||||||
|
c = sys.argv[4][0]
|
||||||
|
else:
|
||||||
|
c = '0'
|
||||||
|
if c == '1':
|
||||||
|
handle_output = handle_output_print_no_exit
|
||||||
|
elif c == '2':
|
||||||
|
handle_output = handle_output_queue
|
||||||
|
elif c == '3':
|
||||||
|
handle_output = handle_output_raise
|
||||||
|
else:
|
||||||
|
handle_output = handle_output_print
|
||||||
|
|
||||||
|
# Action.
|
||||||
|
thread_pool = ThreadPool(
|
||||||
|
my_func,
|
||||||
|
handle_output,
|
||||||
|
nthreads
|
||||||
|
)
|
||||||
|
for work in get_work(min_, max_):
|
||||||
|
error = thread_pool.submit(work)
|
||||||
|
if error is not None:
|
||||||
|
break
|
||||||
|
error = thread_pool.join()
|
||||||
|
if error is not None:
|
||||||
|
print('error: {!r}'.format(error))
|
||||||
|
if handle_output == handle_output_queue:
|
||||||
|
while not out_queue.empty():
|
||||||
|
print(out_queue.get())
|
||||||
9
userland/arch/aarch64/add.S
Normal file
9
userland/arch/aarch64/add.S
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#data-processing-instructions */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
mov x0, 1
|
||||||
|
add x1, x0, 2
|
||||||
|
ASSERT_EQ(x1, 3)
|
||||||
|
EXIT
|
||||||
21
userland/arch/aarch64/adr.S
Normal file
21
userland/arch/aarch64/adr.S
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#adr */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
.data
|
||||||
|
data_label:
|
||||||
|
.word 0x1234678
|
||||||
|
ENTRY
|
||||||
|
/* This is not possible in v7 because the label is in another section.
|
||||||
|
* objdump says that this generates a R_AARCH64_ADR_PRE relocation.
|
||||||
|
* which looks specific to ADR, and therefore makes it more likely
|
||||||
|
* that there was no such relocation in v7.
|
||||||
|
*
|
||||||
|
* This relocation is particularly important because str does not have a
|
||||||
|
* pc-relative mode in ARMv8.
|
||||||
|
*/
|
||||||
|
adr x0, data_label
|
||||||
|
ldr x1, =data_label
|
||||||
|
label:
|
||||||
|
ASSERT_EQ_REG(x0, x1)
|
||||||
|
EXIT
|
||||||
13
userland/arch/aarch64/adrp.S
Normal file
13
userland/arch/aarch64/adrp.S
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#adr */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
adrp x0, label
|
||||||
|
adr x1, label
|
||||||
|
label:
|
||||||
|
/* Clear the lower 12 bits. */
|
||||||
|
bic x1, x1, 0xFF
|
||||||
|
bic x1, x1, 0xF00
|
||||||
|
ASSERT_EQ_REG(x0, x1)
|
||||||
|
EXIT
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#include <assert.h>
|
|
||||||
#include <inttypes.h>
|
|
||||||
|
|
||||||
int main(void) {
|
|
||||||
uint32_t myvar = 1;
|
|
||||||
__asm__ (
|
|
||||||
"add %[myvar], %[myvar], 1;"
|
|
||||||
: [myvar] "=r" (myvar)
|
|
||||||
:
|
|
||||||
:
|
|
||||||
);
|
|
||||||
assert(myvar == 2);
|
|
||||||
}
|
|
||||||
33
userland/arch/aarch64/beq.S
Normal file
33
userland/arch/aarch64/beq.S
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#cbz */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* cbz == 0 */
|
||||||
|
mov x0, 0
|
||||||
|
cbz x0, 1f
|
||||||
|
FAIL
|
||||||
|
1:
|
||||||
|
|
||||||
|
/* cbz != 0 */
|
||||||
|
mov x0, 1
|
||||||
|
cbz x0, 1f
|
||||||
|
b 2f
|
||||||
|
1:
|
||||||
|
FAIL
|
||||||
|
2:
|
||||||
|
|
||||||
|
/* cbnz != 0 */
|
||||||
|
mov x0, 1
|
||||||
|
cbnz x0, 1f
|
||||||
|
FAIL
|
||||||
|
1:
|
||||||
|
|
||||||
|
/* cbnz == 0 */
|
||||||
|
mov x0, 0
|
||||||
|
cbnz x0, 1f
|
||||||
|
b 2f
|
||||||
|
1:
|
||||||
|
FAIL
|
||||||
|
2:
|
||||||
|
EXIT
|
||||||
11
userland/arch/aarch64/bfi.S
Normal file
11
userland/arch/aarch64/bfi.S
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#bfi */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr x0, =0x1122334455667788
|
||||||
|
|
||||||
|
ldr x1, =0xFFFFFFFFFFFFFFFF
|
||||||
|
bfi x1, x0, 16, 32
|
||||||
|
ASSERT_EQ(x1, 0xFFFF55667788FFFF)
|
||||||
|
EXIT
|
||||||
39
userland/arch/aarch64/c/asm_from_c.c
Normal file
39
userland/arch/aarch64/c/asm_from_c.c
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#calling-convention */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
uint64_t my_asm_func(void);
|
||||||
|
/* { return 42; } */
|
||||||
|
__asm__(
|
||||||
|
".global my_asm_func;"
|
||||||
|
"my_asm_func:"
|
||||||
|
"mov x0, 42;"
|
||||||
|
"ret;"
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Now a more complex example that also calls a C function.
|
||||||
|
* We have to store the return value x30 for later because bl modifies it.
|
||||||
|
* https://stackoverflow.com/questions/27941220/push-lr-and-pop-lr-in-arm-arch64/34504752#34504752
|
||||||
|
* We are not modifying any other callee saved register in this function,
|
||||||
|
* since my_c_func is not either (unless GCC has a bug ;-)), so everything else if fine.
|
||||||
|
*/
|
||||||
|
uint64_t my_asm_func_2(void);
|
||||||
|
/* { return my_c_func(); } */
|
||||||
|
__asm__(
|
||||||
|
".global my_asm_func_2;"
|
||||||
|
"my_asm_func_2:"
|
||||||
|
"str x30, [sp, -16]!;"
|
||||||
|
"bl my_c_func;"
|
||||||
|
"ldr x30, [sp], 16;"
|
||||||
|
"ret;"
|
||||||
|
);
|
||||||
|
|
||||||
|
uint64_t my_c_func(void) {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
assert(my_asm_func() == 42);
|
||||||
|
assert(my_asm_func_2() == 42);
|
||||||
|
}
|
||||||
1
userland/arch/aarch64/c/build
Symbolic link
1
userland/arch/aarch64/c/build
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../build
|
||||||
21
userland/arch/aarch64/c/earlyclobber.c
Normal file
21
userland/arch/aarch64/c/earlyclobber.c
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/* An example of using the '&' earlyclobber modifier.
|
||||||
|
* https://stackoverflow.com/questions/15819794/when-to-use-earlyclobber-constraint-in-extended-gcc-inline-assembly/54853663#54853663
|
||||||
|
* The assertion may fail without it. It actually does fail in GCC 8.2.0 at
|
||||||
|
* 34017bcd0bc96a3cf77f6acba4d58350e67c2694 + 1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
uint64_t in = 1;
|
||||||
|
uint64_t out;
|
||||||
|
__asm__ (
|
||||||
|
"add %[out], %[in], 1;"
|
||||||
|
"add %[out], %[in], 1;"
|
||||||
|
: [out] "=&r" (out)
|
||||||
|
: [in] "r" (in)
|
||||||
|
:
|
||||||
|
);
|
||||||
|
assert(out == 2);
|
||||||
|
}
|
||||||
1
userland/arch/aarch64/c/freestanding/build
Symbolic link
1
userland/arch/aarch64/c/freestanding/build
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../build
|
||||||
37
userland/arch/aarch64/c/freestanding/hello.c
Normal file
37
userland/arch/aarch64/c/freestanding/hello.c
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#freestanding-linux-inline-assembly-system-calls */
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
void _start(void) {
|
||||||
|
uint64_t exit_status;
|
||||||
|
|
||||||
|
/* write */
|
||||||
|
{
|
||||||
|
char msg[] = "hello\n";
|
||||||
|
uint64_t syscall_return;
|
||||||
|
register uint64_t x0 __asm__ ("x0") = 1; /* stdout */
|
||||||
|
register char *x1 __asm__ ("x1") = msg;
|
||||||
|
register uint64_t x2 __asm__ ("x2") = sizeof(msg);
|
||||||
|
register uint64_t x8 __asm__ ("x8") = 64; /* syscall number */
|
||||||
|
__asm__ __volatile__ (
|
||||||
|
"svc 0;"
|
||||||
|
: "+r" (x0)
|
||||||
|
: "r" (x1), "r" (x2), "r" (x8)
|
||||||
|
: "memory"
|
||||||
|
);
|
||||||
|
syscall_return = x0;
|
||||||
|
exit_status = (syscall_return != sizeof(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* exit */
|
||||||
|
{
|
||||||
|
register uint64_t x0 __asm__ ("x0") = exit_status;
|
||||||
|
register uint64_t x8 __asm__ ("x8") = 93;
|
||||||
|
__asm__ __volatile__ (
|
||||||
|
"svc 0;"
|
||||||
|
: "+r" (x0)
|
||||||
|
: "r" (x8)
|
||||||
|
:
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
userland/arch/aarch64/c/freestanding/hello_clobbers.c
Normal file
40
userland/arch/aarch64/c/freestanding/hello_clobbers.c
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/* Like hello.c trying to do it without named register variables.
|
||||||
|
* The code is more complicated, and I was not able to get as efficient,
|
||||||
|
* so better just stick to named register variables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
void _start(void) {
|
||||||
|
uint64_t exit_status;
|
||||||
|
|
||||||
|
/* write */
|
||||||
|
{
|
||||||
|
char msg[] = "hello\n";
|
||||||
|
uint64_t syscall_return;
|
||||||
|
__asm__ (
|
||||||
|
"mov x0, 1;" /* stdout */
|
||||||
|
"mov x1, %[msg];"
|
||||||
|
"mov x2, %[len];"
|
||||||
|
"mov x8, 64;" /* syscall number */
|
||||||
|
"svc 0;"
|
||||||
|
"mov %[syscall_return], x0;"
|
||||||
|
: [syscall_return] "=r" (syscall_return)
|
||||||
|
: [msg] "p" (msg),
|
||||||
|
[len] "i" (sizeof(msg))
|
||||||
|
: "x0", "x1", "x2", "x8", "memory"
|
||||||
|
);
|
||||||
|
exit_status = (syscall_return != sizeof(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* exit */
|
||||||
|
__asm__ (
|
||||||
|
"mov x0, %[exit_status];"
|
||||||
|
"mov x8, 93;" /* syscall number */
|
||||||
|
"svc 0;"
|
||||||
|
:
|
||||||
|
: [exit_status] "r" (exit_status)
|
||||||
|
: "x0", "x8"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
13
userland/arch/aarch64/c/inc.c
Normal file
13
userland/arch/aarch64/c/inc.c
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
uint64_t io = 1;
|
||||||
|
__asm__ (
|
||||||
|
"add %[io], %[io], 1;"
|
||||||
|
: [io] "+r" (io)
|
||||||
|
:
|
||||||
|
:
|
||||||
|
);
|
||||||
|
assert(io == 2);
|
||||||
|
}
|
||||||
28
userland/arch/aarch64/c/inc_float.c
Normal file
28
userland/arch/aarch64/c/inc_float.c
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* https://stackoverflow.com/questions/53960240/armv8-floating-point-output-inline-assembly
|
||||||
|
*
|
||||||
|
* We use the undocumented %s and %d modifiers!
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
float my_float = 1.5;
|
||||||
|
__asm__ (
|
||||||
|
"fmov s0, 1.0;"
|
||||||
|
"fadd %s[my_float], %s[my_float], s0;"
|
||||||
|
: [my_float] "+w" (my_float)
|
||||||
|
:
|
||||||
|
: "s0"
|
||||||
|
);
|
||||||
|
assert(my_float == 2.5);
|
||||||
|
|
||||||
|
double my_double = 1.5;
|
||||||
|
__asm__ (
|
||||||
|
"fmov d0, 1.0;"
|
||||||
|
"fadd %d[my_double], %d[my_double], d0;"
|
||||||
|
: [my_double] "+w" (my_double)
|
||||||
|
:
|
||||||
|
: "d0"
|
||||||
|
);
|
||||||
|
assert(my_double == 2.5);
|
||||||
|
}
|
||||||
18
userland/arch/aarch64/c/multiline.cpp
Normal file
18
userland/arch/aarch64/c/multiline.cpp
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// https://stackoverflow.com/questions/3666013/how-to-write-multiline-inline-assembly-code-in-gcc-c/54575948#54575948
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
uint64_t io = 0;
|
||||||
|
__asm__ (
|
||||||
|
R"(
|
||||||
|
add %[io], %[io], #1
|
||||||
|
add %[io], %[io], #1
|
||||||
|
)"
|
||||||
|
: [io] "+r" (io)
|
||||||
|
:
|
||||||
|
:
|
||||||
|
);
|
||||||
|
assert(io == 2);
|
||||||
|
}
|
||||||
27
userland/arch/aarch64/c/reg_var.c
Normal file
27
userland/arch/aarch64/c/reg_var.c
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#register-variables */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
register uint32_t x0 __asm__ ("x0");
|
||||||
|
register uint32_t x1 __asm__ ("x1");
|
||||||
|
uint32_t new_x0;
|
||||||
|
uint32_t new_x1;
|
||||||
|
{
|
||||||
|
x0 = 1;
|
||||||
|
x1 = 2;
|
||||||
|
__asm__ (
|
||||||
|
"add %[x0], x0, #1;"
|
||||||
|
"add %[x1], x1, #1;"
|
||||||
|
: [x0] "+r" (x0),
|
||||||
|
[x1] "+r" (x1)
|
||||||
|
:
|
||||||
|
:
|
||||||
|
);
|
||||||
|
new_x0 = x0;
|
||||||
|
new_x1 = x1;
|
||||||
|
}
|
||||||
|
assert(new_x0 == 2);
|
||||||
|
assert(new_x1 == 3);
|
||||||
|
}
|
||||||
28
userland/arch/aarch64/c/reg_var_float.c
Normal file
28
userland/arch/aarch64/c/reg_var_float.c
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#register-variables */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
register double d0 __asm__ ("d0");
|
||||||
|
register double d1 __asm__ ("d1");
|
||||||
|
double new_d0;
|
||||||
|
double new_d1;
|
||||||
|
{
|
||||||
|
d0 = 1.5;
|
||||||
|
d1 = 2.5;
|
||||||
|
__asm__ (
|
||||||
|
"fmov d2, 1.5;"
|
||||||
|
"fadd %d[d0], d0, d2;"
|
||||||
|
"fadd %d[d1], d1, d2;"
|
||||||
|
: [d0] "+w" (d0),
|
||||||
|
[d1] "+w" (d1)
|
||||||
|
:
|
||||||
|
: "d2"
|
||||||
|
);
|
||||||
|
new_d0 = d0;
|
||||||
|
new_d1 = d1;
|
||||||
|
}
|
||||||
|
assert(new_d0 == 3.0);
|
||||||
|
assert(new_d1 == 4.0);
|
||||||
|
}
|
||||||
19
userland/arch/aarch64/cbz.S
Normal file
19
userland/arch/aarch64/cbz.S
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#cbz */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Branch. */
|
||||||
|
mov x0, 0x0
|
||||||
|
cbz x0, ok
|
||||||
|
FAIL
|
||||||
|
ok:
|
||||||
|
|
||||||
|
/* Don't branch. */
|
||||||
|
mov x0, 0x1
|
||||||
|
cbz x0, ko
|
||||||
|
|
||||||
|
EXIT
|
||||||
|
ko:
|
||||||
|
FAIL
|
||||||
17
userland/arch/aarch64/comments.S
Normal file
17
userland/arch/aarch64/comments.S
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#comments */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
ENTRY
|
||||||
|
# mycomment
|
||||||
|
|
||||||
|
/* ARMv8 has // instead of @ as for comments. */
|
||||||
|
// mycomment
|
||||||
|
nop // mycomment
|
||||||
|
|
||||||
|
/* All these fail. Lol, different than v7, no consistency. */
|
||||||
|
#if 0
|
||||||
|
nop # mycomment
|
||||||
|
@ mycomment
|
||||||
|
nop @ mycomment
|
||||||
|
#endif
|
||||||
|
EXIT
|
||||||
64
userland/arch/aarch64/common_arch.h
Normal file
64
userland/arch/aarch64/common_arch.h
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#ifndef COMMON_ARCH_H
|
||||||
|
#define COMMON_ARCH_H
|
||||||
|
|
||||||
|
#define ASSERT_EQ(reg, const) \
|
||||||
|
ldr x11, =const; \
|
||||||
|
cmp reg, x11; \
|
||||||
|
ASSERT(beq); \
|
||||||
|
;
|
||||||
|
|
||||||
|
#define ASSERT_MEMCMP(s1, s2, n) \
|
||||||
|
MEMCMP(s1, s2, n); \
|
||||||
|
ASSERT_EQ(x0, 0); \
|
||||||
|
;
|
||||||
|
|
||||||
|
#define ENTRY \
|
||||||
|
.text; \
|
||||||
|
.global asm_main; \
|
||||||
|
asm_main: \
|
||||||
|
sub sp, sp, 0xA0; \
|
||||||
|
stp x29, x30, [sp]; \
|
||||||
|
stp x27, x28, [sp, 0x10]; \
|
||||||
|
stp x25, x26, [sp, 0x20]; \
|
||||||
|
stp x23, x24, [sp, 0x30]; \
|
||||||
|
stp x21, x22, [sp, 0x40]; \
|
||||||
|
stp x19, x20, [sp, 0x50]; \
|
||||||
|
stp x6, x7, [sp, 0x60]; \
|
||||||
|
stp x4, x5, [sp, 0x70]; \
|
||||||
|
stp x2, x3, [sp, 0x80]; \
|
||||||
|
stp x0, x1, [sp, 0x90]; \
|
||||||
|
asm_main_after_prologue: \
|
||||||
|
;
|
||||||
|
|
||||||
|
#define EXIT \
|
||||||
|
mov w0, 0; \
|
||||||
|
mov w1, 0; \
|
||||||
|
b pass; \
|
||||||
|
fail: \
|
||||||
|
ldr x1, [sp, 0x90]; \
|
||||||
|
str w0, [x1]; \
|
||||||
|
mov w0, 1; \
|
||||||
|
pass: \
|
||||||
|
ldp x19, x20, [sp, 0x50]; \
|
||||||
|
ldp x21, x22, [sp, 0x40]; \
|
||||||
|
ldp x23, x24, [sp, 0x30]; \
|
||||||
|
ldp x25, x26, [sp, 0x20]; \
|
||||||
|
ldp x27, x28, [sp, 0x10]; \
|
||||||
|
ldp x29, x30, [sp]; \
|
||||||
|
add sp, sp, 0xA0; \
|
||||||
|
ret; \
|
||||||
|
;
|
||||||
|
|
||||||
|
#define FAIL \
|
||||||
|
ldr w0, =__LINE__; \
|
||||||
|
b fail; \
|
||||||
|
;
|
||||||
|
|
||||||
|
#define MEMCMP(s1, s2, n) \
|
||||||
|
adr x0, s1; \
|
||||||
|
adr x1, s2; \
|
||||||
|
ldr x2, =n; \
|
||||||
|
bl memcmp; \
|
||||||
|
;
|
||||||
|
|
||||||
|
#endif
|
||||||
28
userland/arch/aarch64/cset.S
Normal file
28
userland/arch/aarch64/cset.S
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#cset */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* Test values. */
|
||||||
|
mov x0, 0
|
||||||
|
mov x1, 1
|
||||||
|
|
||||||
|
/* eq is true, set x2 = 1. */
|
||||||
|
cmp x0, x0
|
||||||
|
cset x2, eq
|
||||||
|
ASSERT_EQ(x2, 1)
|
||||||
|
|
||||||
|
/* eq is false, set x2 = 0. */
|
||||||
|
cmp x0, x1
|
||||||
|
cset x2, eq
|
||||||
|
ASSERT_EQ(x2, 0)
|
||||||
|
|
||||||
|
/* Same for ne. */
|
||||||
|
cmp x0, x0
|
||||||
|
cset x2, ne
|
||||||
|
ASSERT_EQ(x2, 0)
|
||||||
|
|
||||||
|
cmp x0, x1
|
||||||
|
cset x2, ne
|
||||||
|
ASSERT_EQ(x2, 1)
|
||||||
|
EXIT
|
||||||
1
userland/arch/aarch64/empty.S
Symbolic link
1
userland/arch/aarch64/empty.S
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../empty.S
|
||||||
1
userland/arch/aarch64/fail.S
Symbolic link
1
userland/arch/aarch64/fail.S
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../fail.S
|
||||||
60
userland/arch/aarch64/floating_point.S
Normal file
60
userland/arch/aarch64/floating_point.S
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#advanced-simd-instructions */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* 1.5 + 2.5 == 4.0
|
||||||
|
* using 64-bit double immediates.
|
||||||
|
*/
|
||||||
|
fmov d0, 1.5
|
||||||
|
fmov d1, 2.5
|
||||||
|
fadd d2, d0, d1
|
||||||
|
fmov d3, 4.0
|
||||||
|
/* Unlike VFP vcmp, this stores the status
|
||||||
|
* automatically in the main CPSR.
|
||||||
|
*/
|
||||||
|
fcmp d2, d3
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* Now with a memory stored value. */
|
||||||
|
.data
|
||||||
|
my_double_0:
|
||||||
|
.double 1.5
|
||||||
|
my_double_1:
|
||||||
|
.double 2.5
|
||||||
|
my_double_sum_expect:
|
||||||
|
.double 4.0
|
||||||
|
.text
|
||||||
|
ldr d0, my_double_0
|
||||||
|
ldr d1, my_double_1
|
||||||
|
fadd d2, d0, d1
|
||||||
|
ldr d3, my_double_sum_expect
|
||||||
|
fcmp d2, d3
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* Now in 32-bit. */
|
||||||
|
fmov s0, 1.5
|
||||||
|
fmov s1, 2.5
|
||||||
|
fadd s2, s0, s1
|
||||||
|
fmov s3, 4.0
|
||||||
|
fcmp s2, s3
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* TODO why? What's the point of q then?
|
||||||
|
* Error: operand mismatch -- `fmov q0,1.5'
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
fmov q0, 1.5
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Much like integers, immediates are constrained to
|
||||||
|
* fit in 32-byte instructions. TODO exact rules.
|
||||||
|
*
|
||||||
|
* Assembly here would fail with:
|
||||||
|
*
|
||||||
|
* Error: invalid floating-point constant at operand 2
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
fmov d0, 1.23456798
|
||||||
|
#endif
|
||||||
|
EXIT
|
||||||
1
userland/arch/aarch64/freestanding/build
Symbolic link
1
userland/arch/aarch64/freestanding/build
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../build
|
||||||
20
userland/arch/aarch64/freestanding/hello.S
Normal file
20
userland/arch/aarch64/freestanding/hello.S
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#linux-system-calls */
|
||||||
|
|
||||||
|
.text
|
||||||
|
.global _start
|
||||||
|
_start:
|
||||||
|
asm_main_after_prologue:
|
||||||
|
/* write */
|
||||||
|
mov x0, 1 /* stdout */
|
||||||
|
adr x1, msg /* buffer */
|
||||||
|
ldr x2, =len /* len */
|
||||||
|
mov x8, 64 /* syscall number */
|
||||||
|
svc 0
|
||||||
|
|
||||||
|
/* exit */
|
||||||
|
mov x0, 0 /* exit status */
|
||||||
|
mov x8, 93 /* syscall number */
|
||||||
|
svc 0
|
||||||
|
msg:
|
||||||
|
.ascii "hello\n"
|
||||||
|
len = . - msg
|
||||||
6
userland/arch/aarch64/hello_driver.S
Normal file
6
userland/arch/aarch64/hello_driver.S
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.text
|
||||||
|
.global asm_main
|
||||||
|
asm_main:
|
||||||
|
asm_main_after_prologue:
|
||||||
|
mov w0, 0
|
||||||
|
ret
|
||||||
9
userland/arch/aarch64/immediates.S
Normal file
9
userland/arch/aarch64/immediates.S
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#immediates */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
ENTRY
|
||||||
|
mov x0, 1
|
||||||
|
mov x0, 0x1
|
||||||
|
mov x0, 1
|
||||||
|
mov x0, 0x1
|
||||||
|
EXIT
|
||||||
26
userland/arch/aarch64/movk.S
Normal file
26
userland/arch/aarch64/movk.S
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#movk */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
movk x0, 0x4444, lsl 0
|
||||||
|
movk x0, 0x3333, lsl 16
|
||||||
|
movk x0, 0x2222, lsl 32
|
||||||
|
movk x0, 0x1111, lsl 48
|
||||||
|
ASSERT_EQ(x0, 0x1111222233334444)
|
||||||
|
|
||||||
|
/* Set a label (addresses are 48-bit) with immediates:
|
||||||
|
*
|
||||||
|
* * https://stackoverflow.com/questions/38570495/aarch64-relocation-prefixes
|
||||||
|
* * https://sourceware.org/binutils/docs-2.26/as/AArch64_002dRelocations.html
|
||||||
|
*
|
||||||
|
* This could be used if the label is too far away for
|
||||||
|
* adr relative addressing.
|
||||||
|
*/
|
||||||
|
movz x0, :abs_g2:label /* bits 32-47, overflow check */
|
||||||
|
movk x0, :abs_g1_nc:label /* bits 16-31, no overflow check */
|
||||||
|
movk x0, :abs_g0_nc:label /* bits 0-15, no overflow check */
|
||||||
|
adr x1, label
|
||||||
|
label:
|
||||||
|
ASSERT_EQ_REG(x0, x1)
|
||||||
|
EXIT
|
||||||
9
userland/arch/aarch64/movn.S
Normal file
9
userland/arch/aarch64/movn.S
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#movn */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr x0, =0x123456789ABCDEF0
|
||||||
|
movn x0, 0x8888, lsl 16
|
||||||
|
ASSERT_EQ(x0, 0xFFFFFFFF7777FFFF)
|
||||||
|
EXIT
|
||||||
78
userland/arch/aarch64/pc.S
Normal file
78
userland/arch/aarch64/pc.S
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#registers */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
#if 0
|
||||||
|
/* Unlike v7, we can't use PC like any other register in ARMv8,
|
||||||
|
* since it is not a general purpose register anymore.
|
||||||
|
*
|
||||||
|
* Only branch instructions can modify the PC.
|
||||||
|
*
|
||||||
|
* B1.2.1 "Registers in AArch64 state" says:
|
||||||
|
*
|
||||||
|
* Software cannot write directly to the PC. It
|
||||||
|
* can only be updated on a branch, exception entry or
|
||||||
|
* exception return.
|
||||||
|
*/
|
||||||
|
ldr pc, =10f
|
||||||
|
FAIL
|
||||||
|
10:
|
||||||
|
#endif
|
||||||
|
#if 0
|
||||||
|
mov x0, pc
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* LDR PC-relative loads exist in ARMv8, but they have a separate encoding
|
||||||
|
* "LDR (literal)" instead of "LDR (immediate)":
|
||||||
|
* https://stackoverflow.com/questions/28638981/howto-write-pc-relative-adressing-on-arm-asm/54480999#54480999
|
||||||
|
*/
|
||||||
|
ldr x0, pc_relative_ldr
|
||||||
|
b 1f
|
||||||
|
pc_relative_ldr:
|
||||||
|
.quad 0x123456789ABCDEF0
|
||||||
|
1:
|
||||||
|
ASSERT_EQ(x0, 0x123456789ABCDEF0)
|
||||||
|
|
||||||
|
/* Just for fun, we can also use relative numbers instead of labels.
|
||||||
|
* https://reverseengineering.stackexchange.com/questions/17666/how-does-the-ldr-instruction-work-on-arm/20567#20567
|
||||||
|
*/
|
||||||
|
ldr x0, 0x8
|
||||||
|
b 1f
|
||||||
|
.quad 0x123456789ABCDEF0
|
||||||
|
1:
|
||||||
|
ASSERT_EQ(x0, 0x123456789ABCDEF0)
|
||||||
|
|
||||||
|
/* Analogous for b with PC. */
|
||||||
|
mov x0, 0
|
||||||
|
/* Jumps over mov to ASSERT_EQ. */
|
||||||
|
b 8
|
||||||
|
mov x0, 1
|
||||||
|
ASSERT_EQ(x0, 0)
|
||||||
|
|
||||||
|
/* Trying to use the old "LDR (immediate)" PC-relative
|
||||||
|
* syntax does not work.
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
/* 64-bit integer or SP register expected at operand 2 -- `ldr x0,[pc]' */
|
||||||
|
ldr x0, [pc]
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* There is however no analogue for str. TODO rationale? */
|
||||||
|
#if 0
|
||||||
|
/* Error: invalid addressing mode at operand 2 -- `str x0,pc_relative_str' */
|
||||||
|
str x0, pc_relative_str
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* You just have to use adr + "STR (register)". */
|
||||||
|
ldr x0, pc_relative_str
|
||||||
|
ASSERT_EQ(x0, 0x0)
|
||||||
|
adr x1, pc_relative_str
|
||||||
|
ldr x0, pc_relative_ldr
|
||||||
|
str x0, [x1]
|
||||||
|
ldr x0, pc_relative_str
|
||||||
|
ASSERT_EQ(x0, 0x123456789ABCDEF0)
|
||||||
|
EXIT
|
||||||
|
.data
|
||||||
|
pc_relative_str:
|
||||||
|
.quad 0x0000000000000000
|
||||||
47
userland/arch/aarch64/regs.S
Normal file
47
userland/arch/aarch64/regs.S
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#armv8-registers */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* 31 64-bit eXtended general purpose registers. */
|
||||||
|
mov x0, 0
|
||||||
|
mov x1, 1
|
||||||
|
mov x2, 2
|
||||||
|
mov x3, 3
|
||||||
|
mov x4, 4
|
||||||
|
mov x5, 5
|
||||||
|
mov x6, 6
|
||||||
|
mov x7, 7
|
||||||
|
mov x8, 8
|
||||||
|
mov x9, 9
|
||||||
|
mov x10, 10
|
||||||
|
mov x11, 11
|
||||||
|
mov x12, 12
|
||||||
|
mov x13, 13
|
||||||
|
mov x14, 14
|
||||||
|
mov x15, 15
|
||||||
|
mov x16, 16
|
||||||
|
mov x17, 17
|
||||||
|
mov x18, 18
|
||||||
|
mov x19, 19
|
||||||
|
mov x20, 20
|
||||||
|
mov x21, 21
|
||||||
|
mov x22, 22
|
||||||
|
mov x23, 23
|
||||||
|
mov x24, 24
|
||||||
|
mov x25, 25
|
||||||
|
mov x26, 26
|
||||||
|
mov x27, 27
|
||||||
|
mov x28, 28
|
||||||
|
mov x29, 29
|
||||||
|
|
||||||
|
/* x30 is the link register. BL stores the return address here. */
|
||||||
|
/*mov x30, 30*/
|
||||||
|
|
||||||
|
/* W form addresses the lower 4 bytes word, and zeroes the top. */
|
||||||
|
ldr x0, =0x1111222233334444
|
||||||
|
ldr x1, =0x5555666677778888
|
||||||
|
mov w0, w1
|
||||||
|
ASSERT_EQ(x0, 0x0000000077778888)
|
||||||
|
EXIT
|
||||||
28
userland/arch/aarch64/ret.S
Normal file
28
userland/arch/aarch64/ret.S
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#bl */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
mov x0, 1
|
||||||
|
bl inc
|
||||||
|
ASSERT_EQ(x0, 2)
|
||||||
|
bl inc2
|
||||||
|
ASSERT_EQ(x0, 3)
|
||||||
|
bl inc3
|
||||||
|
ASSERT_EQ(x0, 4)
|
||||||
|
EXIT
|
||||||
|
|
||||||
|
/* void inc(uint64_t *i) { (*i)++ } */
|
||||||
|
inc:
|
||||||
|
add x0, x0, 1
|
||||||
|
ret
|
||||||
|
|
||||||
|
/* Same but explicit return register. */
|
||||||
|
inc2:
|
||||||
|
add x0, x0, 1
|
||||||
|
ret x30
|
||||||
|
|
||||||
|
/* Same but with br. */
|
||||||
|
inc3:
|
||||||
|
add x0, x0, 1
|
||||||
|
br x30
|
||||||
86
userland/arch/aarch64/simd.S
Normal file
86
userland/arch/aarch64/simd.S
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#advanced-simd-instructions */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* 4x 32-bit integer add.
|
||||||
|
*
|
||||||
|
* s stands for single == 32 bits.
|
||||||
|
*
|
||||||
|
* 1 in ld1 means to load just one register, see:
|
||||||
|
* https://github.com/cirosantilli/arm-assembly-cheat#simd-interleaving
|
||||||
|
*/
|
||||||
|
.data
|
||||||
|
u32_0: .word 0xF111F111, 0xF222F222, 0xF333F333, 0xF444F444
|
||||||
|
u32_1: .word 0x15551555, 0x16661666, 0x17771777, 0x18881888
|
||||||
|
u32_sum_expect: .word 0x06670666, 0x08890888, 0x0AAB0AAA, 0x0CCD0CCC
|
||||||
|
.bss
|
||||||
|
u32_sum: .skip 16
|
||||||
|
.text
|
||||||
|
adr x0, u32_0
|
||||||
|
ld1 {v0.4s}, [x0]
|
||||||
|
adr x1, u32_1
|
||||||
|
ld1 {v1.4s}, [x1]
|
||||||
|
add v2.4s, v0.4s, v1.4s
|
||||||
|
adr x0, u32_sum
|
||||||
|
st1 {v2.4s}, [x0]
|
||||||
|
ASSERT_MEMCMP(u32_sum, u32_sum_expect, 0x10)
|
||||||
|
|
||||||
|
/* 2x 64-bit integer add.
|
||||||
|
*
|
||||||
|
* d stands for double == 64 bits.
|
||||||
|
*/
|
||||||
|
.data
|
||||||
|
u64_0: .quad 0xF1111111F1111111, 0xF2222222F2222222
|
||||||
|
u64_1: .quad 0x1555555515555555, 0x1666666616666666
|
||||||
|
u64_sum_expect: .quad 0x0666666706666666, 0x0888888908888888
|
||||||
|
.bss
|
||||||
|
u64_sum: .skip 16
|
||||||
|
.text
|
||||||
|
adr x0, u64_0
|
||||||
|
ld1 {v0.2d}, [x0]
|
||||||
|
adr x1, u64_1
|
||||||
|
ld1 {v1.2d}, [x1]
|
||||||
|
add v2.2d, v0.2d, v1.2d
|
||||||
|
adr x0, u64_sum
|
||||||
|
st1 {v2.2d}, [x0]
|
||||||
|
ASSERT_MEMCMP(u64_sum, u64_sum_expect, 0x10)
|
||||||
|
|
||||||
|
/* 4x 32-bit float add.
|
||||||
|
*
|
||||||
|
* The only difference between the integer point version
|
||||||
|
* is that we use fadd instead of add.
|
||||||
|
*/
|
||||||
|
.data
|
||||||
|
f32_0: .float 1.5, 2.5, 3.5, 4.5
|
||||||
|
f32_1: .float 5.5, 6.5, 7.5, 8.5
|
||||||
|
f32_sum_expect: .float 7.0, 9.0, 11.0, 13.0
|
||||||
|
.bss
|
||||||
|
f32_sum: .skip 16
|
||||||
|
.text
|
||||||
|
adr x0, f32_0
|
||||||
|
ld1 {v0.4s}, [x0]
|
||||||
|
adr x1, f32_1
|
||||||
|
ld1 {v1.4s}, [x1]
|
||||||
|
fadd v2.4s, v0.4s, v1.4s
|
||||||
|
adr x0, f32_sum
|
||||||
|
st1 {v2.4s}, [x0]
|
||||||
|
ASSERT_MEMCMP(f32_sum, f32_sum_expect, 0x10)
|
||||||
|
|
||||||
|
/* 2x 64-bit float add. */
|
||||||
|
.data
|
||||||
|
f64_0: .double 1.5, 2.5
|
||||||
|
f64_1: .double 5.5, 6.5
|
||||||
|
f64_sum_expect: .double 7.0, 9.0
|
||||||
|
.bss
|
||||||
|
f64_sum: .skip 16
|
||||||
|
.text
|
||||||
|
adr x0, f64_0
|
||||||
|
ld1 {v0.2d}, [x0]
|
||||||
|
adr x1, f64_1
|
||||||
|
ld1 {v1.2d}, [x1]
|
||||||
|
fadd v2.2d, v0.2d, v1.2d
|
||||||
|
adr x0, f64_sum
|
||||||
|
st1 {v2.2d}, [x0]
|
||||||
|
ASSERT_MEMCMP(f64_sum, f64_sum_expect, 0x10)
|
||||||
|
EXIT
|
||||||
26
userland/arch/aarch64/simd_interleave.S
Normal file
26
userland/arch/aarch64/simd_interleave.S
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#simd-interleaving */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
.data
|
||||||
|
u32_interleave: .word \
|
||||||
|
0x11111111, 0x55555555, \
|
||||||
|
0x22222222, 0x66666666, \
|
||||||
|
0x33333333, 0x77777777, \
|
||||||
|
0x44444444, 0x88888888
|
||||||
|
u32_interleave_sum_expect: .word \
|
||||||
|
0x66666666, \
|
||||||
|
0x88888888, \
|
||||||
|
0xAAAAAAAA, \
|
||||||
|
0xCCCCCCCC
|
||||||
|
.bss
|
||||||
|
u32_interleave_sum: .skip 16
|
||||||
|
.text
|
||||||
|
adr x0, u32_interleave
|
||||||
|
ld2 {v0.4s, v1.4s}, [x0]
|
||||||
|
add v2.4s, v0.4s, v1.4s
|
||||||
|
adr x0, u32_interleave_sum
|
||||||
|
st1 {v2.4s}, [x0]
|
||||||
|
ASSERT_MEMCMP(u32_interleave_sum, u32_interleave_sum_expect, 0x10)
|
||||||
|
EXIT
|
||||||
13
userland/arch/aarch64/str.S
Normal file
13
userland/arch/aarch64/str.S
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#armv8-str */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr x0, myvar
|
||||||
|
ASSERT_EQ(x0, 0x12346789ABCDEF0)
|
||||||
|
#if 0
|
||||||
|
/* Error: invalid addressing mode at operand 2 -- `str x0,myvar' */
|
||||||
|
str x0, myvar
|
||||||
|
#endif
|
||||||
|
EXIT
|
||||||
|
myvar: .quad 0x12346789ABCDEF0
|
||||||
17
userland/arch/aarch64/ubfm.S
Normal file
17
userland/arch/aarch64/ubfm.S
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#ubfm */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr x0, =0x1122334455667788
|
||||||
|
|
||||||
|
// lsr alias: imms == 63
|
||||||
|
|
||||||
|
ldr x1, =0xFFFFFFFFFFFFFFFF
|
||||||
|
ubfm x1, x0, 16, 63
|
||||||
|
ASSERT_EQ(x1, 0x0000112233445566)
|
||||||
|
|
||||||
|
ldr x1, =0xFFFFFFFFFFFFFFFF
|
||||||
|
ubfm x1, x0, 32, 63
|
||||||
|
ASSERT_EQ(x1, 0x0000000011223344)
|
||||||
|
EXIT
|
||||||
15
userland/arch/aarch64/ubfx.S
Normal file
15
userland/arch/aarch64/ubfx.S
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#ubfx */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr x0, =0x1122334455667788
|
||||||
|
|
||||||
|
ldr x1, =0xFFFFFFFFFFFFFFFF
|
||||||
|
ubfx x1, x0, 8, 16
|
||||||
|
ASSERT_EQ(x1, 0x0000000000006677)
|
||||||
|
|
||||||
|
ldr x1, =0xFFFFFFFFFFFFFFFF
|
||||||
|
ubfx x1, x0, 8, 32
|
||||||
|
ASSERT_EQ(x1, 0x0000000044556677)
|
||||||
|
EXIT
|
||||||
51
userland/arch/aarch64/x31.S
Normal file
51
userland/arch/aarch64/x31.S
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#x31 */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* ERROR: can never use the name x31. */
|
||||||
|
#if 0
|
||||||
|
mov x31, 31
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* mov (register) is an alias for ORR, which accepts xzr. */
|
||||||
|
mov x0, 1
|
||||||
|
mov x0, xzr
|
||||||
|
ASSERT_EQ(x0, 0)
|
||||||
|
|
||||||
|
/* Same encoding as the mov version. */
|
||||||
|
mov x0, 1
|
||||||
|
orr x0, xzr, xzr
|
||||||
|
ASSERT_EQ(x0, 0)
|
||||||
|
|
||||||
|
/* So, orr, which is not an alias, can only take xzr, not sp. */
|
||||||
|
#if 0
|
||||||
|
orr sp, sp, sp
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Zero register discards result if written to. */
|
||||||
|
mov x0, 1
|
||||||
|
orr xzr, x0, x0
|
||||||
|
ASSERT_EQ(xzr, 0)
|
||||||
|
|
||||||
|
/* MOV (to/from SP) is an alias for ADD (immediate). */
|
||||||
|
mov x0, sp
|
||||||
|
mov sp, 1
|
||||||
|
/* Alias to add. */
|
||||||
|
mov x1, sp
|
||||||
|
/* Exact same encoding as above. */
|
||||||
|
add x1, sp, 0
|
||||||
|
ASSERT_EQ(x1, 1)
|
||||||
|
mov sp, x0
|
||||||
|
|
||||||
|
/* So, ADD (immediate), which is not an alias, can only take sp, not xzr. */
|
||||||
|
#if 0
|
||||||
|
/* Error: integer register expected in the extended/shifted operand register at operand 3 -- `add xzr,xzr,1' */
|
||||||
|
add xzr, xzr, 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Note however that ADD (register), unlike ADD (immediate),
|
||||||
|
* does not say anything about SP, and so does accept xzr just fine.
|
||||||
|
*/
|
||||||
|
add xzr, xzr, xzr
|
||||||
|
EXIT
|
||||||
58
userland/arch/arm/add.S
Normal file
58
userland/arch/arm/add.S
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#data-processing-instructions */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Immediate encoding.
|
||||||
|
*
|
||||||
|
* r1 = r0 + 2
|
||||||
|
*/
|
||||||
|
mov r0, 1
|
||||||
|
/* r1 = r0 + 2 */
|
||||||
|
add r1, r0, 2
|
||||||
|
ASSERT_EQ(r1, 3)
|
||||||
|
|
||||||
|
/* If src == dest, we can omit one of them.
|
||||||
|
*
|
||||||
|
* r0 = r0 + 2
|
||||||
|
*/
|
||||||
|
mov r0, 1
|
||||||
|
add r0, 2
|
||||||
|
ASSERT_EQ(r0, 3)
|
||||||
|
|
||||||
|
/* Same as above but explicit. */
|
||||||
|
mov r0, 1
|
||||||
|
add r0, r0, 2
|
||||||
|
ASSERT_EQ(r0, 3)
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* But we cannot omit the register if there is a shift when using .syntx unified:
|
||||||
|
* https://github.com/cirosantilli/arm-assembly-cheat#shift-suffixes
|
||||||
|
*/
|
||||||
|
.syntax unified
|
||||||
|
/* Error: garbage following instruction */
|
||||||
|
add r0, r1, lsl 1
|
||||||
|
/* OK */
|
||||||
|
add r0, r0, r1, lsl 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Register encoding.
|
||||||
|
*
|
||||||
|
* r2 = r0 + r1
|
||||||
|
*/
|
||||||
|
mov r0, 1
|
||||||
|
mov r1, 2
|
||||||
|
add r2, r0, r1
|
||||||
|
ASSERT_EQ(r2, 3)
|
||||||
|
|
||||||
|
/* Register encoding, omit implicit register.
|
||||||
|
*
|
||||||
|
* r1 = r1 + r0
|
||||||
|
*/
|
||||||
|
mov r0, 1
|
||||||
|
mov r1, 2
|
||||||
|
add r1, r0
|
||||||
|
ASSERT_EQ(r1, 3)
|
||||||
|
|
||||||
|
EXIT
|
||||||
51
userland/arch/arm/address_modes.S
Normal file
51
userland/arch/arm/address_modes.S
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#addressing-modes */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Offset mode with immediate. Add 4 to the address register, which ends up
|
||||||
|
* reading myvar2 instead of myvar.
|
||||||
|
*/
|
||||||
|
adr r0, myvar
|
||||||
|
ldr r1, [r0, 4]
|
||||||
|
ASSERT_EQ(r1, 0x9ABCDEF0)
|
||||||
|
/* r0 was not modified. */
|
||||||
|
ASSERT_EQ(r0, myvar)
|
||||||
|
|
||||||
|
/* Pre-indexed mode */
|
||||||
|
adr r0, myvar
|
||||||
|
ldr r1, [r0, 4]!
|
||||||
|
ASSERT_EQ(r1, 0x9ABCDEF0)
|
||||||
|
/* r0 was modified. */
|
||||||
|
ASSERT_EQ(r0, myvar2)
|
||||||
|
|
||||||
|
/* Post-indexed mode */
|
||||||
|
adr r0, myvar
|
||||||
|
ldr r1, [r0], 4
|
||||||
|
ASSERT_EQ(r1, 0x12345678)
|
||||||
|
/* r0 was modified. */
|
||||||
|
ASSERT_EQ(r0, myvar2)
|
||||||
|
|
||||||
|
/* Offset in register. */
|
||||||
|
adr r0, myvar
|
||||||
|
mov r1, 4
|
||||||
|
ldr r2, [r0, r1]
|
||||||
|
ASSERT_EQ(r2, 0x9ABCDEF0)
|
||||||
|
|
||||||
|
/* Offset in shifted register:
|
||||||
|
* r2 =
|
||||||
|
* (r0 + (r1 << 1))
|
||||||
|
* == *(myvar + (2 << 1))
|
||||||
|
* == *(myvar + 4)
|
||||||
|
*/
|
||||||
|
adr r0, myvar
|
||||||
|
mov r1, 2
|
||||||
|
ldr r2, [r0, r1, lsl 1]
|
||||||
|
ASSERT_EQ(r2, 0x9ABCDEF0)
|
||||||
|
|
||||||
|
EXIT
|
||||||
|
myvar:
|
||||||
|
.word 0x12345678
|
||||||
|
myvar2:
|
||||||
|
.word 0x9ABCDEF0
|
||||||
33
userland/arch/arm/adr.S
Normal file
33
userland/arch/arm/adr.S
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#adr */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
.data
|
||||||
|
data_label:
|
||||||
|
.word 0x1234678
|
||||||
|
ENTRY
|
||||||
|
adr r0, label
|
||||||
|
/* objdump tells us that this uses the literal pool,
|
||||||
|
* it does not get converted to adr, which is the better
|
||||||
|
* alternative here.
|
||||||
|
*/
|
||||||
|
adr r1, label
|
||||||
|
adrl r2, label
|
||||||
|
label:
|
||||||
|
ASSERT_EQ_REG(r0, r1)
|
||||||
|
ASSERT_EQ_REG(r0, r2)
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* Error: symbol .data is in a different section.
|
||||||
|
*
|
||||||
|
* It works however in ARMv8.
|
||||||
|
* I think this means that there is no relocation type
|
||||||
|
* that takes care of this encoding in ARMv8, but there
|
||||||
|
* is one in ARMv8.
|
||||||
|
*
|
||||||
|
* If you have no idea what I'm talking about, read this:
|
||||||
|
* https://stackoverflow.com/questions/3322911/what-do-linkers-do/33690144#33690144
|
||||||
|
*/
|
||||||
|
adr r1, data_label
|
||||||
|
#endif
|
||||||
|
EXIT
|
||||||
27
userland/arch/arm/and.S
Normal file
27
userland/arch/arm/and.S
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* Bitwise AND. */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* 0x00 && 0xFF == 0x00 */
|
||||||
|
mov r0, 0x00
|
||||||
|
and r0, 0xFF
|
||||||
|
ASSERT_EQ(r0, 0x00)
|
||||||
|
|
||||||
|
/* 0x0F && 0xF0 == 0x00 */
|
||||||
|
mov r0, 0x0F
|
||||||
|
and r0, 0xF0
|
||||||
|
ASSERT_EQ(r0, 0x00)
|
||||||
|
|
||||||
|
/* 0x0F && 0xFF == 0x0F */
|
||||||
|
mov r0, 0x0F
|
||||||
|
and r0, 0xFF
|
||||||
|
ASSERT_EQ(r0, 0x0F)
|
||||||
|
|
||||||
|
/* 0xF0 && 0xFF == 0xF0 */
|
||||||
|
mov r0, 0xF0
|
||||||
|
and r0, 0xFF
|
||||||
|
ASSERT_EQ(r0, 0xF0)
|
||||||
|
|
||||||
|
EXIT
|
||||||
9
userland/arch/arm/b.S
Normal file
9
userland/arch/arm/b.S
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#b */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
ENTRY
|
||||||
|
/* Jump over the fail. 26-bit PC-relative. */
|
||||||
|
b ok
|
||||||
|
FAIL
|
||||||
|
ok:
|
||||||
|
EXIT
|
||||||
28
userland/arch/arm/beq.S
Normal file
28
userland/arch/arm/beq.S
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#beq */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Smaller*/
|
||||||
|
mov r0, 1
|
||||||
|
cmp r0, 2
|
||||||
|
ASSERT(ble)
|
||||||
|
ASSERT(blt)
|
||||||
|
ASSERT(bne)
|
||||||
|
|
||||||
|
/* Equal. */
|
||||||
|
mov r1, 0
|
||||||
|
cmp r1, 0
|
||||||
|
ASSERT(beq)
|
||||||
|
ASSERT(bge)
|
||||||
|
ASSERT(ble)
|
||||||
|
|
||||||
|
/* Greater. */
|
||||||
|
mov r0, 2
|
||||||
|
cmp r0, 1
|
||||||
|
ASSERT(bge)
|
||||||
|
ASSERT(bgt)
|
||||||
|
ASSERT(bne)
|
||||||
|
|
||||||
|
EXIT
|
||||||
10
userland/arch/arm/bfi.S
Normal file
10
userland/arch/arm/bfi.S
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#bfi */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr r0, =0x11223344
|
||||||
|
ldr r1, =0xFFFFFFFF
|
||||||
|
bfi r1, r0, 8, 16
|
||||||
|
ASSERT_EQ(r1, 0xFF3344FF)
|
||||||
|
EXIT
|
||||||
10
userland/arch/arm/bic.S
Normal file
10
userland/arch/arm/bic.S
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#bic */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* 0x0F & ~0x55 == 0x0F & 0xAA == 0x0A */
|
||||||
|
mov r0, 0x0F
|
||||||
|
bic r0, 0x55
|
||||||
|
ASSERT_EQ(r0, 0x0A)
|
||||||
|
EXIT
|
||||||
14
userland/arch/arm/bl.S
Normal file
14
userland/arch/arm/bl.S
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#bl */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
mov r0, 1
|
||||||
|
bl inc
|
||||||
|
ASSERT_EQ(r0, 2)
|
||||||
|
EXIT
|
||||||
|
|
||||||
|
/* void inc(int *i) { (*i)++ } */
|
||||||
|
inc:
|
||||||
|
add r0, 1
|
||||||
|
bx lr
|
||||||
1
userland/arch/arm/build
Symbolic link
1
userland/arch/arm/build
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../build
|
||||||
17
userland/arch/arm/c/add.c
Normal file
17
userland/arch/arm/c/add.c
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* 1 + 2 == 3 */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
uint32_t in0 = 1, in1 = 2, out;
|
||||||
|
__asm__ (
|
||||||
|
"add %[out], %[in0], %[in1];"
|
||||||
|
: [out] "=r" (out)
|
||||||
|
: [in0] "r" (in0),
|
||||||
|
[in1] "r" (in1)
|
||||||
|
);
|
||||||
|
assert(in0 == 1);
|
||||||
|
assert(in1 == 2);
|
||||||
|
assert(out == 3);
|
||||||
|
}
|
||||||
1
userland/arch/arm/c/build
Symbolic link
1
userland/arch/arm/c/build
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../build
|
||||||
1
userland/arch/arm/c/freestanding/build
Symbolic link
1
userland/arch/arm/c/freestanding/build
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../build
|
||||||
35
userland/arch/arm/c/freestanding/hello.c
Normal file
35
userland/arch/arm/c/freestanding/hello.c
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
void _start(void) {
|
||||||
|
uint32_t exit_status;
|
||||||
|
|
||||||
|
/* write */
|
||||||
|
{
|
||||||
|
char msg[] = "hello\n";
|
||||||
|
uint32_t syscall_return;
|
||||||
|
register uint32_t r0 __asm__ ("r0") = 1; /* stdout */
|
||||||
|
register char *r1 __asm__ ("r1") = msg;
|
||||||
|
register uint32_t r2 __asm__ ("r2") = sizeof(msg);
|
||||||
|
register uint32_t r8 __asm__ ("r7") = 4; /* syscall number */
|
||||||
|
__asm__ __volatile__ (
|
||||||
|
"svc 0;"
|
||||||
|
: "+r" (r0)
|
||||||
|
: "r" (r1), "r" (r2), "r" (r8)
|
||||||
|
: "memory"
|
||||||
|
);
|
||||||
|
syscall_return = r0;
|
||||||
|
exit_status = (syscall_return != sizeof(msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* exit */
|
||||||
|
{
|
||||||
|
register uint32_t r0 __asm__ ("r0") = exit_status;
|
||||||
|
register uint32_t r7 __asm__ ("r7") = 1;
|
||||||
|
__asm__ __volatile__ (
|
||||||
|
"svc 0;"
|
||||||
|
: "+r" (r0)
|
||||||
|
: "r" (r7)
|
||||||
|
:
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
userland/arch/arm/c/inc.c
Normal file
15
userland/arch/arm/c/inc.c
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* Increment a variable in inline assembly. */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
uint32_t my_local_var = 1;
|
||||||
|
__asm__ (
|
||||||
|
"add %[my_local_var], %[my_local_var], #1;"
|
||||||
|
: [my_local_var] "+r" (my_local_var)
|
||||||
|
:
|
||||||
|
:
|
||||||
|
);
|
||||||
|
assert(my_local_var == 2);
|
||||||
|
}
|
||||||
28
userland/arch/arm/c/inc_float.c
Normal file
28
userland/arch/arm/c/inc_float.c
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/* https://stackoverflow.com/questions/53960240/armv8-floating-point-output-inline-assembly */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
float my_float = 1.5;
|
||||||
|
__asm__ (
|
||||||
|
"vmov s0, 1.0;"
|
||||||
|
"vadd.f32 %[my_float], %[my_float], s0;"
|
||||||
|
: [my_float] "+t" (my_float)
|
||||||
|
:
|
||||||
|
: "s0"
|
||||||
|
);
|
||||||
|
assert(my_float == 2.5);
|
||||||
|
|
||||||
|
/* Undocumented %P
|
||||||
|
* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=89482
|
||||||
|
*/
|
||||||
|
double my_double = 1.5;
|
||||||
|
__asm__ (
|
||||||
|
"vmov.f64 d0, 1.0;"
|
||||||
|
"vadd.f64 %P[my_double], %P[my_double], d0;"
|
||||||
|
: [my_double] "+w" (my_double)
|
||||||
|
:
|
||||||
|
: "d0"
|
||||||
|
);
|
||||||
|
assert(my_double == 2.5);
|
||||||
|
}
|
||||||
32
userland/arch/arm/c/inc_memory.c
Normal file
32
userland/arch/arm/c/inc_memory.c
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/* Like inc.c but less good since we do more work ourselves.
|
||||||
|
*
|
||||||
|
* Just doing this to test out the "m" memory constraint.
|
||||||
|
*
|
||||||
|
* GCC 8.2.0 -O0 assembles ldr line to:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* ldr r0, [fp, #-12]
|
||||||
|
* ....
|
||||||
|
*
|
||||||
|
* and `-O3` assembles to:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* ldr r0, [sp]
|
||||||
|
* ....
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
uint32_t my_local_var = 1;
|
||||||
|
__asm__ (
|
||||||
|
"ldr r0, %[my_local_var];"
|
||||||
|
"add r0, r0, #1;"
|
||||||
|
"str r0, %[my_local_var];"
|
||||||
|
: [my_local_var] "+m" (my_local_var)
|
||||||
|
:
|
||||||
|
: "r0"
|
||||||
|
);
|
||||||
|
assert(my_local_var == 2);
|
||||||
|
}
|
||||||
25
userland/arch/arm/c/inc_memory_global.c
Normal file
25
userland/arch/arm/c/inc_memory_global.c
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/* GCC 8.2.0 -O0 and -O3 assembles ldr line to:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* movw r3, #<lower address part>
|
||||||
|
* movt r3, #<higher address part>
|
||||||
|
* ldr r0, [r3]
|
||||||
|
* ....
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
uint32_t my_global_var = 1;
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
__asm__ (
|
||||||
|
"ldr r0, %[my_global_var];"
|
||||||
|
"add r0, r0, #1;"
|
||||||
|
"str r0, %[my_global_var];"
|
||||||
|
: [my_global_var] "+m" (my_global_var)
|
||||||
|
:
|
||||||
|
: "r0"
|
||||||
|
);
|
||||||
|
assert(my_global_var == 2);
|
||||||
|
}
|
||||||
38
userland/arch/arm/c/reg_var.c
Normal file
38
userland/arch/arm/c/reg_var.c
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#register-variables */
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
register uint32_t r0 __asm__ ("r0");
|
||||||
|
register uint32_t r1 __asm__ ("r1");
|
||||||
|
uint32_t new_r0;
|
||||||
|
uint32_t new_r1;
|
||||||
|
{
|
||||||
|
/* We must set the registers immediately before calling,
|
||||||
|
* without making any function calls in between.
|
||||||
|
*/
|
||||||
|
r0 = 1;
|
||||||
|
r1 = 2;
|
||||||
|
__asm__ (
|
||||||
|
/* We intentionally use an explicit r0 and r1 here,
|
||||||
|
* just to illustrate that we are certain that the
|
||||||
|
* r0 variable will go in r0. Real code would never do this.
|
||||||
|
*/
|
||||||
|
"add %[r0], r0, #1;"
|
||||||
|
"add %[r1], r1, #1;"
|
||||||
|
/* We have to specify r0 in the constraints.*/
|
||||||
|
: [r0] "+r" (r0),
|
||||||
|
[r1] "+r" (r1)
|
||||||
|
:
|
||||||
|
:
|
||||||
|
);
|
||||||
|
/* When we are done, we must immediatly assign
|
||||||
|
* the register variables to regular variables.
|
||||||
|
*/
|
||||||
|
new_r0 = r0;
|
||||||
|
new_r1 = r1;
|
||||||
|
}
|
||||||
|
assert(new_r0 == 2);
|
||||||
|
assert(new_r1 == 3);
|
||||||
|
}
|
||||||
59
userland/arch/arm/c_from_asm.S
Normal file
59
userland/arch/arm/c_from_asm.S
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#calling-convention */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
.data
|
||||||
|
puts_s:
|
||||||
|
.asciz "hello puts"
|
||||||
|
printf_format:
|
||||||
|
.asciz "hello printf %x\n"
|
||||||
|
my_array_0:
|
||||||
|
.word 0x11111111, 0x22222222, 0x33333333, 0x44444444
|
||||||
|
my_array_1:
|
||||||
|
.word 0x55555555, 0x66666666, 0x77777777, 0x88888888
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* puts("hello world") */
|
||||||
|
/* r0 is first argument. */
|
||||||
|
ldr r0, =puts_s
|
||||||
|
bl puts
|
||||||
|
/* Check exit statut >= 0 for success. */
|
||||||
|
cmp r0, 0
|
||||||
|
ASSERT(bge)
|
||||||
|
|
||||||
|
/* printf */
|
||||||
|
ldr r0, =printf_format
|
||||||
|
ldr r1, =0x12345678
|
||||||
|
bl printf
|
||||||
|
cmp r0, 0
|
||||||
|
ASSERT(bge)
|
||||||
|
|
||||||
|
/* memcpy and memcmp. */
|
||||||
|
|
||||||
|
/* Smaller. */
|
||||||
|
ldr r0, =my_array_0
|
||||||
|
ldr r1, =my_array_1
|
||||||
|
ldr r2, =0x10
|
||||||
|
bl memcmp
|
||||||
|
cmp r0, 0
|
||||||
|
ASSERT(blt)
|
||||||
|
|
||||||
|
/* Copy. */
|
||||||
|
ldr r0, =my_array_0
|
||||||
|
ldr r1, =my_array_1
|
||||||
|
ldr r2, =0x10
|
||||||
|
bl memcpy
|
||||||
|
|
||||||
|
/* Equal. */
|
||||||
|
ldr r0, =my_array_0
|
||||||
|
ldr r1, =my_array_1
|
||||||
|
ldr r2, =0x10
|
||||||
|
bl memcmp
|
||||||
|
ASSERT_EQ(r0, 0)
|
||||||
|
|
||||||
|
/* exit(0) */
|
||||||
|
mov r0, 0
|
||||||
|
bl exit
|
||||||
|
|
||||||
|
/* Never reached, just for the fail symbol. */
|
||||||
|
EXIT
|
||||||
17
userland/arch/arm/clz.S
Normal file
17
userland/arch/arm/clz.S
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#data-processing-instructions */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr r0, =0x7FFFFFFF
|
||||||
|
clz r1, r0
|
||||||
|
ASSERT_EQ(r1, 1)
|
||||||
|
|
||||||
|
ldr r0, =0x3FFFFFFF
|
||||||
|
clz r1, r0
|
||||||
|
ASSERT_EQ(r1, 2)
|
||||||
|
|
||||||
|
ldr r0, =0x1FFFFFFF
|
||||||
|
clz r1, r0
|
||||||
|
ASSERT_EQ(r1, 3)
|
||||||
|
EXIT
|
||||||
14
userland/arch/arm/comments.S
Normal file
14
userland/arch/arm/comments.S
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#comments */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
ENTRY
|
||||||
|
# mycomment
|
||||||
|
@ mycomment
|
||||||
|
/* # only works at the beginning of the line.
|
||||||
|
* Error: garbage following instruction -- `nop #comment'
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
nop # mycomment
|
||||||
|
#endif
|
||||||
|
nop @ mycomment
|
||||||
|
EXIT
|
||||||
71
userland/arch/arm/common_arch.h
Normal file
71
userland/arch/arm/common_arch.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#ifndef COMMON_ARCH_H
|
||||||
|
#define COMMON_ARCH_H
|
||||||
|
|
||||||
|
.syntax unified
|
||||||
|
|
||||||
|
/* Assert that a register equals a constant.
|
||||||
|
* * reg: the register to check. Can be r0-r10, but not r11. r11 is overwritten.
|
||||||
|
* * const: the constant to compare to. Only works for literals or labels, not for registers.
|
||||||
|
* For register / register comparision, use ASSERT_EQ_REG.
|
||||||
|
*/
|
||||||
|
#define ASSERT_EQ(reg, const) \
|
||||||
|
ldr r11, =const; \
|
||||||
|
cmp reg, r11; \
|
||||||
|
ASSERT(beq); \
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Assert that two arrays are the same. */
|
||||||
|
#define ASSERT_MEMCMP(s1, s2, n) \
|
||||||
|
MEMCMP(s1, s2, n); \
|
||||||
|
ASSERT_EQ(r0, 0); \
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Store all callee saved registers, and LR in case we make further BL calls.
|
||||||
|
*
|
||||||
|
* Also save the input arguments r0-r3 on the stack, so we can access them later on,
|
||||||
|
* despite those registers being overwritten.
|
||||||
|
*/
|
||||||
|
#define ENTRY \
|
||||||
|
.text; \
|
||||||
|
.global asm_main; \
|
||||||
|
asm_main: \
|
||||||
|
stmdb sp!, {r0-r12, lr}; \
|
||||||
|
asm_main_after_prologue: \
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Meant to be called at the end of ENTRY.*
|
||||||
|
*
|
||||||
|
* Branching to "fail" makes tests fail with exit status 1.
|
||||||
|
*
|
||||||
|
* If EXIT is reached, the program ends successfully.
|
||||||
|
*
|
||||||
|
* Restore LR and bx jump to it to return from asm_main.
|
||||||
|
*/
|
||||||
|
#define EXIT \
|
||||||
|
mov r0, 0; \
|
||||||
|
mov r1, 0; \
|
||||||
|
b pass; \
|
||||||
|
fail: \
|
||||||
|
ldr r1, [sp]; \
|
||||||
|
str r0, [r1]; \
|
||||||
|
mov r0, 1; \
|
||||||
|
pass: \
|
||||||
|
add sp, 16; \
|
||||||
|
ldmia sp!, {r4-r12, lr}; \
|
||||||
|
bx lr; \
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Always fail. */
|
||||||
|
#define FAIL \
|
||||||
|
ldr r0, =__LINE__; \
|
||||||
|
b fail; \
|
||||||
|
;
|
||||||
|
|
||||||
|
#define MEMCMP(s1, s2, n) \
|
||||||
|
ldr r0, =s1; \
|
||||||
|
ldr r1, =s2; \
|
||||||
|
ldr r2, =n; \
|
||||||
|
bl memcmp; \
|
||||||
|
;
|
||||||
|
|
||||||
|
#endif
|
||||||
16
userland/arch/arm/cond.S
Normal file
16
userland/arch/arm/cond.S
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#conditional-execution */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
mov r0, 0
|
||||||
|
mov r1, 1
|
||||||
|
cmp r0, 1
|
||||||
|
/* Previous cmp failed, skip this operation. */
|
||||||
|
addeq r1, 1
|
||||||
|
ASSERT_EQ(r1, 1)
|
||||||
|
cmp r0, 0
|
||||||
|
/* Previous passed, do this operation. */
|
||||||
|
addeq r1, 1
|
||||||
|
ASSERT_EQ(r1, 2)
|
||||||
|
EXIT
|
||||||
1
userland/arch/arm/empty.S
Symbolic link
1
userland/arch/arm/empty.S
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../empty.S
|
||||||
1
userland/arch/arm/fail.S
Symbolic link
1
userland/arch/arm/fail.S
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../fail.S
|
||||||
1
userland/arch/arm/freestanding/build
Symbolic link
1
userland/arch/arm/freestanding/build
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../build
|
||||||
21
userland/arch/arm/freestanding/hello.S
Normal file
21
userland/arch/arm/freestanding/hello.S
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#linux-system-calls */
|
||||||
|
|
||||||
|
.syntax unified
|
||||||
|
.text
|
||||||
|
.global _start
|
||||||
|
_start:
|
||||||
|
asm_main_after_prologue:
|
||||||
|
/* write */
|
||||||
|
mov r0, 1 /* stdout */
|
||||||
|
adr r1, msg /* buffer */
|
||||||
|
ldr r2, =len /* len */
|
||||||
|
mov r7, 4 /* syscall number */
|
||||||
|
svc 0
|
||||||
|
|
||||||
|
/* exit */
|
||||||
|
mov r0, 0 /* exit status */
|
||||||
|
mov r7, 1 /* syscall number */
|
||||||
|
svc 0
|
||||||
|
msg:
|
||||||
|
.ascii "hello\n"
|
||||||
|
len = . - msg
|
||||||
23
userland/arch/arm/hello_driver.S
Normal file
23
userland/arch/arm/hello_driver.S
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* Minimal example using driver.
|
||||||
|
*
|
||||||
|
* Controls the exit status of the program.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.syntax unified
|
||||||
|
.text
|
||||||
|
.global asm_main
|
||||||
|
asm_main:
|
||||||
|
asm_main_after_prologue:
|
||||||
|
|
||||||
|
/* Set the return value according to the ARM calling convention. */
|
||||||
|
mov r0, 0
|
||||||
|
|
||||||
|
/* Try some whacky value to see tests break. */
|
||||||
|
/*mov r0, 77*/
|
||||||
|
|
||||||
|
/* Branch to the address at register lr.
|
||||||
|
* That is the return value which was put there by the C driver (likely with a bl).
|
||||||
|
*
|
||||||
|
* X means eXchange encoding from thumb back to ARM, which is what the driver uses.
|
||||||
|
*/
|
||||||
|
bx lr
|
||||||
24
userland/arch/arm/immediates.S
Normal file
24
userland/arch/arm/immediates.S
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#immediates */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* This is the default. We hack it in common.h however. */
|
||||||
|
.syntax divided
|
||||||
|
/* These fail. */
|
||||||
|
#if 0
|
||||||
|
mov r0, 1
|
||||||
|
mov r0, 0x1
|
||||||
|
#endif
|
||||||
|
mov r0, #1
|
||||||
|
mov r0, #0x1
|
||||||
|
mov r0, $1
|
||||||
|
mov r0, $0x1
|
||||||
|
.syntax unified
|
||||||
|
mov r0, 1
|
||||||
|
mov r0, 0x1
|
||||||
|
mov r0, 1
|
||||||
|
mov r0, 0x1
|
||||||
|
mov r0, $1
|
||||||
|
mov r0, $0x1
|
||||||
|
EXIT
|
||||||
27
userland/arch/arm/inc_array.S
Normal file
27
userland/arch/arm/inc_array.S
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#loop-over-array */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#define NELEM 4
|
||||||
|
#define ELEM_SIZE 4
|
||||||
|
|
||||||
|
.data;
|
||||||
|
my_array:
|
||||||
|
.word 0x11111111, 0x22222222, 0x33333333, 0x44444444
|
||||||
|
my_array_expect:
|
||||||
|
.word 0x11111112, 0x22222223, 0x33333334, 0x44444445
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* Increment. */
|
||||||
|
ldr r0, =my_array
|
||||||
|
mov r1, NELEM
|
||||||
|
increment:
|
||||||
|
ldr r2, [r0]
|
||||||
|
add r2, 1
|
||||||
|
/* Post index usage. */
|
||||||
|
str r2, [r0], ELEM_SIZE
|
||||||
|
sub r1, 1
|
||||||
|
cmp r1, 0
|
||||||
|
bne increment
|
||||||
|
ASSERT_MEMCMP(my_array, my_array_expect, 0x10)
|
||||||
|
EXIT
|
||||||
62
userland/arch/arm/ldmia.S
Normal file
62
userland/arch/arm/ldmia.S
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#loop-over-array */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
#define NELEM 4
|
||||||
|
#define ELEM_SIZE 4
|
||||||
|
|
||||||
|
.data;
|
||||||
|
my_array_0:
|
||||||
|
.word 0x11111111, 0x22222222, 0x33333333, 0x44444444
|
||||||
|
my_array_1:
|
||||||
|
.word 0x55555555, 0x66666666, 0x77777777, 0x88888888
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Load r1, r2, r3 and r4 starting from the address in r0. Don't change r0 */
|
||||||
|
ldr r0, =my_array_0
|
||||||
|
ldr r1, =0
|
||||||
|
ldr r2, =0
|
||||||
|
ldr r3, =0
|
||||||
|
ldr r4, =0
|
||||||
|
ldmia r0, {r1-r4}
|
||||||
|
ASSERT_EQ(r0, my_array_0)
|
||||||
|
ASSERT_EQ(r1, 0x11111111)
|
||||||
|
ASSERT_EQ(r2, 0x22222222)
|
||||||
|
ASSERT_EQ(r3, 0x33333333)
|
||||||
|
ASSERT_EQ(r4, 0x44444444)
|
||||||
|
|
||||||
|
/* Swapping the order of r1 and r2 on the mnemonic makes no difference to load order.
|
||||||
|
*
|
||||||
|
* But it gives an assembler warning, so we won't do it by default:
|
||||||
|
*
|
||||||
|
* ldmia.S: Assembler messages:
|
||||||
|
* ldmia.S:32: Warning: register range not in ascending order
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
ldr r0, =my_array_0
|
||||||
|
ldr r1, =0
|
||||||
|
ldr r2, =0
|
||||||
|
ldmia r0, {r2,r1}
|
||||||
|
ASSERT_EQ(r1, 0x11111111)
|
||||||
|
ASSERT_EQ(r2, 0x22222222)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Modify the array */
|
||||||
|
ldr r0, =my_array_1
|
||||||
|
ldr r1, =0x55555555
|
||||||
|
ldr r2, =0x66666666
|
||||||
|
ldr r3, =0x77777777
|
||||||
|
ldr r4, =0x88888888
|
||||||
|
stmdb r0, {r1-r4}
|
||||||
|
|
||||||
|
/* Verify that my_array_0 changed and is equal to my_array_1. */
|
||||||
|
MEMCMP(my_array_0, my_array_1, 0x10)
|
||||||
|
ASSERT_EQ(r0, 0)
|
||||||
|
|
||||||
|
/* Load registers and increment r0. */
|
||||||
|
ldr r0, =my_array_0
|
||||||
|
ldmia r0!, {r1-r4}
|
||||||
|
ASSERT_EQ(r0, my_array_1)
|
||||||
|
|
||||||
|
EXIT
|
||||||
65
userland/arch/arm/ldr_pseudo.S
Normal file
65
userland/arch/arm/ldr_pseudo.S
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#ldr-pseudo-instruction */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Mnemonic for a PC relative load:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* ldr r0, [pc, offset]
|
||||||
|
* r0 = myvar
|
||||||
|
* ....
|
||||||
|
*/
|
||||||
|
ldr r0, myvar
|
||||||
|
ASSERT_EQ(r0, 0x12345678)
|
||||||
|
|
||||||
|
/* Mnemonic PC relative load with an offset.
|
||||||
|
* Load myvar2 instead of myvar.
|
||||||
|
*/
|
||||||
|
ldr r0, myvar + 4
|
||||||
|
ASSERT_EQ(r0, 0x9ABCDEF0)
|
||||||
|
|
||||||
|
/* First store the address in r0 using a magic =myvar, which creates
|
||||||
|
* a new variable containing the address and PC-relative addresses it
|
||||||
|
* https://stackoverflow.com/questions/17214962/what-is-the-difference-between-label-equals-sign-and-label-brackets-in-ar
|
||||||
|
*
|
||||||
|
* Use the adr instruction would likely be better for this application however.
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* r0 = &myvar
|
||||||
|
* r1 = *r0
|
||||||
|
* ....
|
||||||
|
*/
|
||||||
|
ldr r0, =myvar
|
||||||
|
ldr r1, [r0]
|
||||||
|
ASSERT_EQ(r1, 0x12345678)
|
||||||
|
|
||||||
|
/* More efficiently, use r0 as the address to read, and write to r0 itself. */
|
||||||
|
ldr r0, =myvar
|
||||||
|
ldr r0, [r0]
|
||||||
|
ASSERT_EQ(r0, 0x12345678)
|
||||||
|
|
||||||
|
/* Same as =myvar but store a constant to a register.
|
||||||
|
* Can also be done with movw and movt. */
|
||||||
|
ldr r0, =0x11112222
|
||||||
|
ASSERT_EQ(r0, 0x11112222)
|
||||||
|
|
||||||
|
/* We can also use GAS tolower16 and topper16 and movw and movt
|
||||||
|
* to load the address of myvar into r0 with two immediates.
|
||||||
|
*
|
||||||
|
* This results in one extra 4 byte instruction read from memory,
|
||||||
|
* and one less data read, so it is likely more cache efficient.
|
||||||
|
*
|
||||||
|
* https://sourceware.org/binutils/docs-2.19/as/ARM_002dRelocations.html
|
||||||
|
*/
|
||||||
|
movw r0, #:lower16:myvar
|
||||||
|
movt r0, #:upper16:myvar
|
||||||
|
ldr r1, [r0]
|
||||||
|
ASSERT_EQ(r1, 0x12345678)
|
||||||
|
|
||||||
|
EXIT
|
||||||
|
myvar:
|
||||||
|
.word 0x12345678
|
||||||
|
myvar2:
|
||||||
|
.word 0x9ABCDEF0
|
||||||
12
userland/arch/arm/ldrb.S
Normal file
12
userland/arch/arm/ldrb.S
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#ldrh-and-ldrb */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr r0, =myvar
|
||||||
|
mov r1, 0x0
|
||||||
|
ldrb r1, [r0]
|
||||||
|
ASSERT_EQ(r1, 0x00000078)
|
||||||
|
EXIT
|
||||||
|
myvar:
|
||||||
|
.word 0x12345678
|
||||||
12
userland/arch/arm/ldrh.S
Normal file
12
userland/arch/arm/ldrh.S
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#ldrh-and-ldrb */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr r0, =myvar
|
||||||
|
mov r1, 0x0
|
||||||
|
ldrh r1, [r0]
|
||||||
|
ASSERT_EQ(r1, 0x00005678)
|
||||||
|
EXIT
|
||||||
|
myvar:
|
||||||
|
.word 0x12345678
|
||||||
19
userland/arch/arm/mov.S
Normal file
19
userland/arch/arm/mov.S
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#mov */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Immediate. */
|
||||||
|
mov r0, 0
|
||||||
|
ASSERT_EQ(r0, 0)
|
||||||
|
mov r0, 1
|
||||||
|
ASSERT_EQ(r0, 1)
|
||||||
|
|
||||||
|
/* Register. */
|
||||||
|
mov r0, 0
|
||||||
|
mov r1, 1
|
||||||
|
mov r1, r0
|
||||||
|
ASSERT_EQ(r1, 0)
|
||||||
|
|
||||||
|
EXIT
|
||||||
27
userland/arch/arm/movw.S
Normal file
27
userland/arch/arm/movw.S
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#movw-and-movt */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* movt (top) and movw (TODO what is w) set the higher
|
||||||
|
* and lower 16 bits of the register.
|
||||||
|
*/
|
||||||
|
movw r0, 0xFFFF
|
||||||
|
movt r0, 0x1234
|
||||||
|
add r0, 1
|
||||||
|
ASSERT_EQ(r0, 0x12350000)
|
||||||
|
|
||||||
|
/* movw also zeroes out the top bits, allowing small 16-bit
|
||||||
|
* C constants to be assigned in a single instruction.
|
||||||
|
*
|
||||||
|
* It differs from mov because mov can only encode 8 bits
|
||||||
|
* at a time, while movw can encode 16.
|
||||||
|
*
|
||||||
|
* movt does not modify the lower bits however.
|
||||||
|
*/
|
||||||
|
ldr r0, =0x12345678
|
||||||
|
movw r0, 0x1111
|
||||||
|
ASSERT_EQ(r0, 0x00001111)
|
||||||
|
|
||||||
|
EXIT
|
||||||
12
userland/arch/arm/mul.S
Normal file
12
userland/arch/arm/mul.S
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/* Multiplication. */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* 2 * 3 = 6 */
|
||||||
|
mov r0, 0
|
||||||
|
mov r1, 2
|
||||||
|
mov r2, 3
|
||||||
|
mul r1, r2
|
||||||
|
ASSERT_EQ(r1, 6)
|
||||||
|
EXIT
|
||||||
32
userland/arch/arm/nop.S
Normal file
32
userland/arch/arm/nop.S
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#nop */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* Disassembles as:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* nop {0}
|
||||||
|
* ....
|
||||||
|
*
|
||||||
|
* TODO what is the `{0}`?
|
||||||
|
*/
|
||||||
|
nop
|
||||||
|
|
||||||
|
/* Disassembles as:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* nop ; (mov r0, r0)
|
||||||
|
* ....
|
||||||
|
*/
|
||||||
|
mov r0, r0
|
||||||
|
|
||||||
|
/* Disassemble as mov. TODO Why not as nop as in `mov r0, r0`?
|
||||||
|
* Do they have any effect?
|
||||||
|
*/
|
||||||
|
mov r1, r1
|
||||||
|
mov r8, r8
|
||||||
|
|
||||||
|
/* And there are other nops as well? Disassembles as `and`. */
|
||||||
|
and r0, r0, r0
|
||||||
|
EXIT
|
||||||
31
userland/arch/arm/push.S
Normal file
31
userland/arch/arm/push.S
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#ldmia */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Save sp before push. */
|
||||||
|
mov r0, sp
|
||||||
|
|
||||||
|
/* Push. */
|
||||||
|
mov r1, 1
|
||||||
|
mov r2, 2
|
||||||
|
push {r1, r2}
|
||||||
|
|
||||||
|
/* Save sp after push. */
|
||||||
|
mov r1, sp
|
||||||
|
|
||||||
|
/* Restore. */
|
||||||
|
mov r3, 0
|
||||||
|
mov r4, 0
|
||||||
|
pop {r3, r4}
|
||||||
|
ASSERT_EQ(r3, 1)
|
||||||
|
ASSERT_EQ(r4, 2)
|
||||||
|
|
||||||
|
/* Check that stack pointer moved down by 8 bytes
|
||||||
|
* (2 registers x 4 bytes each).
|
||||||
|
*/
|
||||||
|
sub r0, r1
|
||||||
|
ASSERT_EQ(r0, 8)
|
||||||
|
|
||||||
|
EXIT
|
||||||
9
userland/arch/arm/rbit.S
Normal file
9
userland/arch/arm/rbit.S
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#rbit */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
ldr r0, =0b00000001001000110100010101100101
|
||||||
|
rbit r1, r0
|
||||||
|
ASSERT_EQ(r1, 0b10100110101000101100010010000000)
|
||||||
|
EXIT
|
||||||
69
userland/arch/arm/regs.S
Normal file
69
userland/arch/arm/regs.S
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#registers */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* 13 general purpose registers. */
|
||||||
|
mov r0, 0
|
||||||
|
mov r1, 1
|
||||||
|
mov r2, 2
|
||||||
|
mov r3, 3
|
||||||
|
mov r4, 4
|
||||||
|
mov r5, 5
|
||||||
|
mov r6, 6
|
||||||
|
mov r7, 7
|
||||||
|
mov r8, 8
|
||||||
|
mov r9, 9
|
||||||
|
mov r10, 10
|
||||||
|
mov r11, 11
|
||||||
|
mov r12, 12
|
||||||
|
|
||||||
|
/* * r11: aliased to FP (frame pointer, debug stack trace usage only)
|
||||||
|
* +
|
||||||
|
* I think FP is only a convention with no instruction impact, but TODO:
|
||||||
|
* not mentioned on AAPCS. aarch64 AAPCS mentions it though.
|
||||||
|
* * r13: aliased to SP (stack pointer), what push / pop use
|
||||||
|
* * r14: aliased to LR (link register), what bl writes the return address to
|
||||||
|
* * r15: aliased to PC (program counter), contains the current instruction address
|
||||||
|
*
|
||||||
|
* In ARMv8, SP and PC have dedicated registers in addition to
|
||||||
|
* the 32-general purpose ones. LR is still general purpose as before.
|
||||||
|
*
|
||||||
|
* Therefore, it is possible to use those registers in any place
|
||||||
|
* other registers may be used.
|
||||||
|
*
|
||||||
|
* This is not possible in ARMv8 anymore.
|
||||||
|
*
|
||||||
|
* For example, we can load an address into PC, which is very similar to what B / BX does:
|
||||||
|
* https://stackoverflow.com/questions/32304646/arm-assembly-branch-to-address-inside-register-or-memory/54145818#54145818
|
||||||
|
*/
|
||||||
|
ldr pc, =10f
|
||||||
|
FAIL
|
||||||
|
10:
|
||||||
|
|
||||||
|
/* Same with r15, which is the same as pc. */
|
||||||
|
ldr r15, =10f
|
||||||
|
FAIL
|
||||||
|
10:
|
||||||
|
|
||||||
|
/* Another example with mov reading from pc. */
|
||||||
|
pc_addr:
|
||||||
|
mov r0, pc
|
||||||
|
/* Why sub 8:
|
||||||
|
* https://stackoverflow.com/questions/24091566/why-does-the-arm-pc-register-point-to-the-instruction-after-the-next-one-to-be-e
|
||||||
|
*/
|
||||||
|
sub r0, r0, 8
|
||||||
|
|
||||||
|
/* pc-relative load also just work just like any other register. */
|
||||||
|
ldr r0, [pc]
|
||||||
|
b 1f
|
||||||
|
.word 0x12345678
|
||||||
|
1:
|
||||||
|
ASSERT_EQ(r0, 0x12345678)
|
||||||
|
|
||||||
|
/* We can also use fp in GNU GAS assembly. */
|
||||||
|
mov r11, 0
|
||||||
|
mov fp, 1
|
||||||
|
ASSERT_EQ(r11, 1)
|
||||||
|
EXIT
|
||||||
15
userland/arch/arm/rev.S
Normal file
15
userland/arch/arm/rev.S
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#data-processing-instructions */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* All bytes in register. */
|
||||||
|
ldr r0, =0x11223344
|
||||||
|
rev r1, r0
|
||||||
|
ASSERT_EQ(r1, 0x44332211)
|
||||||
|
|
||||||
|
/* Groups of 16-bits. */
|
||||||
|
ldr r0, =0x11223344
|
||||||
|
rev16 r1, r0
|
||||||
|
ASSERT_EQ(r1, 0x22114433)
|
||||||
|
EXIT
|
||||||
35
userland/arch/arm/s_suffix.S
Normal file
35
userland/arch/arm/s_suffix.S
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#s-suffix */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* Result is 0, set beq. */
|
||||||
|
movs r0, 0
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* The opposite. */
|
||||||
|
movs r0, 1
|
||||||
|
ASSERT(bne)
|
||||||
|
|
||||||
|
/* mov without s does not set the status. */
|
||||||
|
movs r0, 0
|
||||||
|
mov r0, 1
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* movs still moves... */
|
||||||
|
mov r0, 0
|
||||||
|
movs r0, 1
|
||||||
|
ASSERT_EQ(r0, 1)
|
||||||
|
|
||||||
|
/* add: the result is 0. */
|
||||||
|
mov r0, 1
|
||||||
|
adds r0, -1
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* add: result non 0. */
|
||||||
|
mov r0, 1
|
||||||
|
adds r0, 1
|
||||||
|
ASSERT(bne)
|
||||||
|
|
||||||
|
EXIT
|
||||||
79
userland/arch/arm/shift.S
Normal file
79
userland/arch/arm/shift.S
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#shift-suffixes */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* lsr */
|
||||||
|
ldr r0, =0xFFF00FFF
|
||||||
|
mov r1, r0, lsl 8
|
||||||
|
ldr r2, =0xF00FFF00
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* lsl */
|
||||||
|
ldr r0, =0xFFF00FFF
|
||||||
|
mov r1, r0, lsr 8
|
||||||
|
ldr r2, =0x00FFF00F
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* ror */
|
||||||
|
ldr r0, =0xFFF00FFF
|
||||||
|
mov r1, r0, ror 8
|
||||||
|
ldr r2, =0xFFFFF00F
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* asr negative */
|
||||||
|
ldr r0, =0x80000008
|
||||||
|
mov r1, r0, asr 1
|
||||||
|
ldr r2, =0xC0000004
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* asr positive */
|
||||||
|
ldr r0, =0x40000008
|
||||||
|
mov r1, r0, asr 1
|
||||||
|
ldr r2, =0x20000004
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* There are also direct shift mnemonics for the mov shifts.
|
||||||
|
*
|
||||||
|
* They assembly to the exact same bytes as the mov version
|
||||||
|
*/
|
||||||
|
ldr r0, =0xFFF00FFF
|
||||||
|
lsl r1, r0, 8
|
||||||
|
ldr r2, =0xF00FFF00
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* If used with the `mov` instruction, it results in a pure shift,
|
||||||
|
* but the suffixes also exist for all the other data processing instructions.
|
||||||
|
*
|
||||||
|
* Here we illustrate a shifted add instruction which calculates:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* r1 = r1 + (r0 << 1)
|
||||||
|
* ....
|
||||||
|
*/
|
||||||
|
ldr r0, =0x10
|
||||||
|
ldr r1, =0x100
|
||||||
|
add r1, r1, r0, lsl 1
|
||||||
|
ldr r2, =0x00000120
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* The shift takes up the same encoding slot as the immediate,
|
||||||
|
* therefore it is not possible to both use an immediate and shift.
|
||||||
|
*
|
||||||
|
* Error: shift expression expected -- `add r1,r0,1,lsl#1'
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
add r1, r0, 1, lsl 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* However, you can still encode shifted bitmasks of
|
||||||
|
* limited width in immediates, so why not just use the
|
||||||
|
* assembler pre-processing for it?
|
||||||
|
*/
|
||||||
|
ldr r1, =0x100
|
||||||
|
add r1, r1, (0x10 << 1)
|
||||||
|
ldr r2, =0x00000120
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
EXIT
|
||||||
113
userland/arch/arm/simd.S
Normal file
113
userland/arch/arm/simd.S
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#advanced-simd-instructions */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* vadd.u32
|
||||||
|
*
|
||||||
|
* Add 4x 32-bit unsigned integers in one go.
|
||||||
|
*
|
||||||
|
* q means 128-bits.
|
||||||
|
*
|
||||||
|
* u32 means that we treat memory as uint32_t types.
|
||||||
|
*
|
||||||
|
* 4 is deduced: in 128 bits you can fit 4 u32.
|
||||||
|
*
|
||||||
|
* Observe how the carry is propagated within u32 integers,
|
||||||
|
* but not across them.
|
||||||
|
*/
|
||||||
|
.data
|
||||||
|
u32_0: .word 0xF111F111, 0xF222F222, 0xF333F333, 0xF444F444
|
||||||
|
u32_1: .word 0x15551555, 0x16661666, 0x17771777, 0x18881888
|
||||||
|
u32_sum_expect: .word 0x06670666, 0x08890888, 0x0AAB0AAA, 0x0CCD0CCC
|
||||||
|
.bss
|
||||||
|
u32_sum: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =u32_0
|
||||||
|
vld1.32 {q0}, [r0]
|
||||||
|
ldr r0, =u32_1
|
||||||
|
vld1.32 {q1}, [r0]
|
||||||
|
vadd.u32 q2, q0, q1
|
||||||
|
ldr r0, =u32_sum
|
||||||
|
vst1.u32 {q2}, [r0]
|
||||||
|
ASSERT_MEMCMP(u32_sum, u32_sum_expect, 0x10)
|
||||||
|
|
||||||
|
/* vadd.u64: 2x 64-bit unsigned integer add. */
|
||||||
|
.data
|
||||||
|
u64_0: .quad 0xF1111111F1111111, 0xF2222222F2222222
|
||||||
|
u64_1: .quad 0x1555555515555555, 0x1666666616666666
|
||||||
|
u64_sum_expect: .quad 0x0666666706666666, 0x0888888908888888
|
||||||
|
.bss
|
||||||
|
u64_sum: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =u64_0
|
||||||
|
vld1.64 {q0}, [r0]
|
||||||
|
ldr r0, =u64_1
|
||||||
|
vld1.64 {q1}, [r0]
|
||||||
|
vadd.u64 q2, q0, q1
|
||||||
|
ldr r0, =u64_sum
|
||||||
|
vst1.u64 {q2}, [r0]
|
||||||
|
ASSERT_MEMCMP(u64_sum, u64_sum_expect, 0x10)
|
||||||
|
|
||||||
|
/* vadd.s64: 2x 64-bit signed integer add. TODO: how to differentiate
|
||||||
|
* it from signed? I think signed and unsigned addition are identical
|
||||||
|
* in two's complement, the only difference is overflow / carry detection
|
||||||
|
* flags. But how do flags work when there are many values being added
|
||||||
|
* at once?
|
||||||
|
*/
|
||||||
|
.data
|
||||||
|
s64_0: .quad -1, -2
|
||||||
|
s64_1: .quad -1, -2
|
||||||
|
s64_sum_expect: .quad -2, -4
|
||||||
|
.bss
|
||||||
|
s64_sum: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =s64_0
|
||||||
|
vld1.64 {q0}, [r0]
|
||||||
|
ldr r0, =s64_1
|
||||||
|
vld1.64 {q1}, [r0]
|
||||||
|
vadd.s64 q2, q0, q1
|
||||||
|
ldr r0, =s64_sum
|
||||||
|
vst1.s64 {q2}, [r0]
|
||||||
|
ASSERT_MEMCMP(s64_sum, s64_sum_expect, 0x10)
|
||||||
|
|
||||||
|
/* vadd.f32: 4x 32-bit float add. */
|
||||||
|
.data
|
||||||
|
f32_0: .float 1.5, 2.5, 3.5, 4.5
|
||||||
|
f32_1: .float 5.5, 6.5, 7.5, 8.5
|
||||||
|
f32_sum_expect: .float 7.0, 9.0, 11.0, 13.0
|
||||||
|
.bss
|
||||||
|
f32_sum: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =f32_0
|
||||||
|
vld1.32 {q0}, [r0]
|
||||||
|
ldr r0, =f32_1
|
||||||
|
vld1.32 {q1}, [r0]
|
||||||
|
vadd.f32 q2, q0, q1
|
||||||
|
ldr r0, =f32_sum
|
||||||
|
vst1.32 {q2}, [r0]
|
||||||
|
ASSERT_MEMCMP(f32_sum, f32_sum_expect, 0x10)
|
||||||
|
|
||||||
|
/* vadd.f64: 2x 64-bit float add: appears not possible.
|
||||||
|
*
|
||||||
|
* https://stackoverflow.com/questions/36052564/does-arm-support-simd-operations-for-64-bit-floating-point-numbers
|
||||||
|
*/
|
||||||
|
.data
|
||||||
|
f64_0: .double 1.5, 2.5
|
||||||
|
f64_1: .double 5.5, 6.5
|
||||||
|
f64_sum_expect: .double 7.0, 9.0
|
||||||
|
.bss
|
||||||
|
f64_sum: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =f64_0
|
||||||
|
vld1.64 {q0}, [r0]
|
||||||
|
ldr r0, =f64_1
|
||||||
|
vld1.64 {q1}, [r0]
|
||||||
|
#if 0
|
||||||
|
/* bad type in Neon instruction -- `vadd.f64 q2,q0,q1' */
|
||||||
|
vadd.f64 q2, q0, q1
|
||||||
|
ldr r0, =f64_sum
|
||||||
|
vst1.64 {q2}, [r0]
|
||||||
|
ASSERT_MEMCMP(f64_sum, f64_sum_expect, 0x10)
|
||||||
|
#endif
|
||||||
|
EXIT
|
||||||
60
userland/arch/arm/str.S
Normal file
60
userland/arch/arm/str.S
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#load-and-store-instructions */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
.data;
|
||||||
|
/* Must be in the .data section, since we want to modify it. */
|
||||||
|
myvar:
|
||||||
|
.word 0x12345678
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* r0 will contain the address. */
|
||||||
|
ldr r0, =myvar
|
||||||
|
|
||||||
|
/* Sanity check. */
|
||||||
|
ldr r1, [r0]
|
||||||
|
movw r2, 0x5678
|
||||||
|
movt r2, 0x1234
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* Modify the value. */
|
||||||
|
movw r1, 0xDEF0
|
||||||
|
movt r1, 0x9ABC
|
||||||
|
str r1, [r0]
|
||||||
|
|
||||||
|
/* Check that it changed. */
|
||||||
|
ldr r1, [r0]
|
||||||
|
movw r2, 0xDEF0
|
||||||
|
movt r2, 0x9ABC
|
||||||
|
ASSERT_EQ_REG(r1, r2)
|
||||||
|
|
||||||
|
/* Cannot use PC relative addressing to a different segment,
|
||||||
|
* or else it fails with:
|
||||||
|
*
|
||||||
|
* ....
|
||||||
|
* Error: internal_relocation (type: OFFSET_IMM) not fixed up
|
||||||
|
* ....
|
||||||
|
*
|
||||||
|
* https://stackoverflow.com/questions/10094282/internal-relocation-not-fixed-up
|
||||||
|
*/
|
||||||
|
/*ldr r0, myvar*/
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* We could in theory write this to set the address of myvar,
|
||||||
|
* but it will always segfault under Linux because the text segment is read-only.
|
||||||
|
* This is however useful in baremetal programming.
|
||||||
|
* This construct is not possible in ARMv8 for str:
|
||||||
|
* https://github.com/cirosantilli/arm-assembly-cheat#armv8-str
|
||||||
|
*/
|
||||||
|
str r1, var_in_same_section
|
||||||
|
var_in_same_section:
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* = sign just doesn't make sense for str, you can't set the
|
||||||
|
* address of a variable.
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
str r1, =myvar
|
||||||
|
#endif
|
||||||
|
|
||||||
|
EXIT
|
||||||
11
userland/arch/arm/sub.S
Normal file
11
userland/arch/arm/sub.S
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* Subtraction. */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* 3 - 2 == 1 , register version.*/
|
||||||
|
mov r0, 3
|
||||||
|
mov r1, 2
|
||||||
|
sub r0, r0, r1
|
||||||
|
ASSERT_EQ(r0, 1)
|
||||||
|
EXIT
|
||||||
17
userland/arch/arm/thumb.S
Normal file
17
userland/arch/arm/thumb.S
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* Illustrates features that are only available in thumb. */
|
||||||
|
|
||||||
|
.syntax unified
|
||||||
|
.text
|
||||||
|
.thumb_func
|
||||||
|
.global asm_main
|
||||||
|
asm_main:
|
||||||
|
asm_main_after_prologue:
|
||||||
|
|
||||||
|
/* CBZ: cmp and branch if zero instruction. Equivalent to CMP + BEQ.
|
||||||
|
* TODO create an interesting assertion here.
|
||||||
|
*/
|
||||||
|
cbz r1, 1f
|
||||||
|
1:
|
||||||
|
|
||||||
|
mov r0, 0
|
||||||
|
bx lr
|
||||||
19
userland/arch/arm/tst.S
Normal file
19
userland/arch/arm/tst.S
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* Test. Same as ands, but don't store the result, just update flags. */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
|
||||||
|
/* 0x0F && 0xF0 == 0x00, so beq. */
|
||||||
|
mov r0, 0x0F
|
||||||
|
tst r0, 0xF0
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* bne */
|
||||||
|
mov r0, 0xFF
|
||||||
|
tst r0, 0x0F
|
||||||
|
ASSERT(bne)
|
||||||
|
# r0 was not modified.
|
||||||
|
ASSERT_EQ(r0, 0xFF)
|
||||||
|
|
||||||
|
EXIT
|
||||||
90
userland/arch/arm/vcvt.S
Normal file
90
userland/arch/arm/vcvt.S
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#vcvt */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* SIMD positive. */
|
||||||
|
.data
|
||||||
|
vcvt_positive_0: .float 1.25, 2.5, 3.75, 4.0
|
||||||
|
vcvt_positive_expect: .word 1, 2, 3, 4
|
||||||
|
.bss
|
||||||
|
vcvt_positive_result: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =vcvt_positive_0
|
||||||
|
vld1.32 {q0}, [r0]
|
||||||
|
vcvt.u32.f32 q1, q0
|
||||||
|
ldr r0, =vcvt_positive_result
|
||||||
|
vst1.32 {q1}, [r0]
|
||||||
|
ASSERT_MEMCMP(vcvt_positive_result, vcvt_positive_expect, 0x10)
|
||||||
|
|
||||||
|
/* SIMD negative. */
|
||||||
|
.data
|
||||||
|
vcvt_negative_0: .float -1.25, -2.5, -3.75, -4.0
|
||||||
|
vcvt_negative_expect: .word -1, -2, -3, -4
|
||||||
|
.bss
|
||||||
|
vcvt_negative_result: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =vcvt_negative_0
|
||||||
|
vld1.32 {q0}, [r0]
|
||||||
|
vcvt.s32.f32 q1, q0
|
||||||
|
ldr r0, =vcvt_negative_result
|
||||||
|
vst1.32 {q1}, [r0]
|
||||||
|
ASSERT_MEMCMP(vcvt_negative_result, vcvt_negative_expect, 0x10)
|
||||||
|
|
||||||
|
/* Floating point. */
|
||||||
|
.data
|
||||||
|
vcvt_positive_float_0: .float 1.5, 2.5
|
||||||
|
vcvt_positive_float_expect: .word 1
|
||||||
|
.float 2.5
|
||||||
|
.bss
|
||||||
|
vcvt_positive_float_result: .skip 0x8
|
||||||
|
.text
|
||||||
|
ldr r0, =vcvt_positive_float_0
|
||||||
|
vld1.32 {d0}, [r0]
|
||||||
|
vcvt.u32.f32 s0, s0
|
||||||
|
ldr r0, =vcvt_positive_float_result
|
||||||
|
vst1.32 {d0}, [r0]
|
||||||
|
ASSERT_MEMCMP(vcvt_positive_float_result, vcvt_positive_float_expect, 0x8)
|
||||||
|
|
||||||
|
/* Floating point but with immediates.
|
||||||
|
*
|
||||||
|
* You have to worry of course about representability of
|
||||||
|
* the immediate in 4 bytes, which is even more fun for
|
||||||
|
* floating point numbers :-)
|
||||||
|
*
|
||||||
|
* Doing this mostly to illustrate the joys of vmov.i32.
|
||||||
|
*
|
||||||
|
* For some reason, there is no vmov.i32 sn, only dn.
|
||||||
|
* If you try to use sn, it does the same as .f32 and
|
||||||
|
* stores a float instead. Horrible!
|
||||||
|
*/
|
||||||
|
vmov.f32 d0, 1.5
|
||||||
|
vcvt.u32.f32 s0, s0
|
||||||
|
vmov.i32 d1, 1
|
||||||
|
vcmp.f32 s0, s2
|
||||||
|
vmrs apsr_nzcv, fpscr
|
||||||
|
ASSERT(beq)
|
||||||
|
/* Check that s1 wasn't modified by vcvt. */
|
||||||
|
vmov.f32 s2, 1.5
|
||||||
|
vcmp.f32 s1, s2
|
||||||
|
vmrs apsr_nzcv, fpscr
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* Floating point double precision. */
|
||||||
|
.data
|
||||||
|
vcvt_positive_double_0: .double 1.5
|
||||||
|
vcvt_positive_double_expect: .word 1
|
||||||
|
.bss
|
||||||
|
vcvt_positive_double_result: .skip 0x8
|
||||||
|
.text
|
||||||
|
ldr r0, =vcvt_positive_double_0
|
||||||
|
vld1.64 {d0}, [r0]
|
||||||
|
vcvt.u32.f64 s0, d0
|
||||||
|
ldr r0, =vcvt_positive_double_result
|
||||||
|
vst1.32 {d0}, [r0]
|
||||||
|
ASSERT_MEMCMP(
|
||||||
|
vcvt_positive_double_result,
|
||||||
|
vcvt_positive_double_expect,
|
||||||
|
0x4
|
||||||
|
)
|
||||||
|
EXIT
|
||||||
41
userland/arch/arm/vcvta.S
Normal file
41
userland/arch/arm/vcvta.S
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#vcvta */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* SIMD positive. */
|
||||||
|
.data
|
||||||
|
vcvta_positive_0: .float 1.25, 2.5, 3.75, 4.0
|
||||||
|
vcvta_positive_expect: .word 1, 3, 4, 4
|
||||||
|
.bss
|
||||||
|
vcvta_positive_result: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =vcvta_positive_0
|
||||||
|
vld1.32 {q0}, [r0]
|
||||||
|
vcvta.u32.f32 q1, q0
|
||||||
|
ldr r0, =vcvta_positive_result
|
||||||
|
vst1.32 {q1}, [r0]
|
||||||
|
ASSERT_MEMCMP(
|
||||||
|
vcvta_positive_result,
|
||||||
|
vcvta_positive_expect,
|
||||||
|
0x10
|
||||||
|
)
|
||||||
|
|
||||||
|
/* SIMD negative. */
|
||||||
|
.data
|
||||||
|
vcvta_negative_0: .float -1.25, -2.5, -3.75, -4.0
|
||||||
|
vcvta_negative_expect: .word -1, -3, -4, -4
|
||||||
|
.bss
|
||||||
|
vcvta_negative_result: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =vcvta_negative_0
|
||||||
|
vld1.32 {q0}, [r0]
|
||||||
|
vcvta.s32.f32 q1, q0
|
||||||
|
ldr r0, =vcvta_negative_result
|
||||||
|
vst1.32 {q1}, [r0]
|
||||||
|
ASSERT_MEMCMP(
|
||||||
|
vcvta_negative_result,
|
||||||
|
vcvta_negative_expect,
|
||||||
|
0x10
|
||||||
|
)
|
||||||
|
EXIT
|
||||||
46
userland/arch/arm/vcvtr.S
Normal file
46
userland/arch/arm/vcvtr.S
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#vcvtrr */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
.data
|
||||||
|
vcvtr_0: .float 1.25, 2.5, 3.75, 4.0
|
||||||
|
vcvtr_expect_zero: .word 1, 2, 3, 4
|
||||||
|
vcvtr_expect_plus_infinity: .word 2, 3, 4, 4
|
||||||
|
.bss
|
||||||
|
vcvtr_result_zero: .skip 0x10
|
||||||
|
vcvtr_result_plus_infinity: .skip 0x10
|
||||||
|
.text
|
||||||
|
ldr r0, =vcvtr_0
|
||||||
|
vld1.32 {q0}, [r0]
|
||||||
|
|
||||||
|
/* zero */
|
||||||
|
vmrs r0, fpscr
|
||||||
|
orr r0, r0, (3 << 22)
|
||||||
|
vmsr fpscr, r0
|
||||||
|
vcvtr.u32.f32 q1, q0
|
||||||
|
ldr r0, =vcvtr_result_zero
|
||||||
|
vst1.32 {q1}, [r0]
|
||||||
|
ASSERT_MEMCMP(
|
||||||
|
vcvtr_result_zero,
|
||||||
|
vcvtr_expect_zero,
|
||||||
|
0x10
|
||||||
|
)
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* TODO why is this not working? Rounds to zero still. */
|
||||||
|
/* plus infinity */
|
||||||
|
vmrs r0, fpscr
|
||||||
|
mov r1, 1
|
||||||
|
bfi r0, r1, 22, 2
|
||||||
|
vmsr fpscr, r0
|
||||||
|
vcvtr.u32.f32 q1, q0
|
||||||
|
ldr r0, =vcvtr_result_plus_infinity
|
||||||
|
vst1.32 {q1}, [r0]
|
||||||
|
ASSERT_MEMCMP(
|
||||||
|
vcvtr_result_plus_infinity,
|
||||||
|
vcvtr_expect_plus_infinity,
|
||||||
|
0x10
|
||||||
|
)
|
||||||
|
#endif
|
||||||
|
EXIT
|
||||||
152
userland/arch/arm/vfp.S
Normal file
152
userland/arch/arm/vfp.S
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/* https://github.com/cirosantilli/arm-assembly-cheat#vfp
|
||||||
|
* Adapted from: https://mindplusplus.wordpress.com/2013/06/27/arm-vfp-vector-programming-part-2-examples/ */
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
.data;
|
||||||
|
a1:
|
||||||
|
.float 0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5
|
||||||
|
a2:
|
||||||
|
.float 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5
|
||||||
|
sum:
|
||||||
|
.skip 32
|
||||||
|
sum_expect:
|
||||||
|
.float 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0
|
||||||
|
|
||||||
|
ENTRY
|
||||||
|
/* Minimal single precision floating point example.
|
||||||
|
* TODO: floating point representation constraints due to 4-byte instruction?
|
||||||
|
*/
|
||||||
|
vmov s0, 1.5
|
||||||
|
vmov s1, 2.5
|
||||||
|
vadd.f32 s2, s0, s1
|
||||||
|
vmov s3, 4.0
|
||||||
|
/* Compare two floating point registers. Stores results in fpscr:
|
||||||
|
* (floating point status and control register).
|
||||||
|
*/
|
||||||
|
vcmp.f32 s2, s3
|
||||||
|
/* Move the nzcv bits from fpscr to apsr */
|
||||||
|
vmrs apsr_nzcv, fpscr
|
||||||
|
/* This branch uses the Z bit of apsr, which was set accordingly. */
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* Now the same from memory with vldr and vstr. */
|
||||||
|
.data
|
||||||
|
my_float_0:
|
||||||
|
.float 1.5
|
||||||
|
my_float_1:
|
||||||
|
.float 2.5
|
||||||
|
my_float_sum_expect:
|
||||||
|
.float 4.0
|
||||||
|
.bss
|
||||||
|
my_float_sum:
|
||||||
|
.skip 4
|
||||||
|
.text
|
||||||
|
ldr r0, =my_float_0
|
||||||
|
vldr s0, [r0]
|
||||||
|
ldr r0, =my_float_1
|
||||||
|
vldr s1, [r0]
|
||||||
|
vadd.f32 s2, s0, s1
|
||||||
|
ldr r0, =my_float_sum
|
||||||
|
vstr.f32 s2, [r0]
|
||||||
|
ASSERT_MEMCMP(my_float_sum, my_float_sum_expect, 4)
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* We can't do pseudo vldr as for ldr, fails with:
|
||||||
|
* Error: cannot represent CP_OFF_IMM relocation in this object file format
|
||||||
|
* It works on ARMv8 however, so the relocation must have been added.
|
||||||
|
*/
|
||||||
|
vldr s0, my_float_0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Minimal double precision floating point example. */
|
||||||
|
vmov.f64 d0, 1.5
|
||||||
|
vmov.f64 d1, 2.5
|
||||||
|
vadd.f64 d2, d0, d1
|
||||||
|
vmov.f64 d3, 4.0
|
||||||
|
vcmp.f64 d2, d3
|
||||||
|
vmrs apsr_nzcv, fpscr
|
||||||
|
ASSERT(beq)
|
||||||
|
|
||||||
|
/* vmov can also move to general purpose registers.
|
||||||
|
*
|
||||||
|
* Just remember that we can't use float immediates with general purpose registers:
|
||||||
|
* https://stackoverflow.com/questions/6514537/how-do-i-specify-immediate-floating-point-numbers-with-inline-assembly/52906126#52906126
|
||||||
|
*/
|
||||||
|
mov r1, 2
|
||||||
|
mov r0, 1
|
||||||
|
vmov s0, r0
|
||||||
|
vmov s1, s0
|
||||||
|
vmov r1, s1
|
||||||
|
ASSERT_EQ_REG(r0, r1)
|
||||||
|
|
||||||
|
/* Now a more complex test function. */
|
||||||
|
ldr r0, =sum
|
||||||
|
ldr r1, =a1
|
||||||
|
ldr r2, =a2
|
||||||
|
mov r3, 8
|
||||||
|
bl vec_sum
|
||||||
|
/* The assert works easily because all floats used
|
||||||
|
* have exact base-2 representation.
|
||||||
|
*/
|
||||||
|
ASSERT_MEMCMP(sum, sum_expect, 0x20)
|
||||||
|
EXIT
|
||||||
|
|
||||||
|
/* void vec_sum(float *sum, float *a1, float *a2, int length) {
|
||||||
|
* int i;
|
||||||
|
* for (i=0; i < length; i++)
|
||||||
|
* *(sum+i) = *(a1+i) + *(a2+i);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
vec_sum:
|
||||||
|
/* Setup */
|
||||||
|
push {r0, r1, r4, lr}
|
||||||
|
push {r0, r1}
|
||||||
|
mov r0, 1
|
||||||
|
mov r1, 8
|
||||||
|
bl reconfig
|
||||||
|
pop {r0, r1}
|
||||||
|
asr r3, 3
|
||||||
|
|
||||||
|
/* Do the sum. */
|
||||||
|
1:
|
||||||
|
fldmias r1!, {s8-s15}
|
||||||
|
fldmias r2!, {s16-s23}
|
||||||
|
vadd.f32 s24, s8, s16
|
||||||
|
fstmias r0!, {s24-s31}
|
||||||
|
subs r3, r3, 1
|
||||||
|
bne 1b
|
||||||
|
|
||||||
|
/* Teardown. */
|
||||||
|
bl deconfig
|
||||||
|
pop {r0, r1, r4, pc}
|
||||||
|
|
||||||
|
/* inputs:
|
||||||
|
* r0: desired vector stride (1 or 2)
|
||||||
|
* r1: desired vector length (min. 1, max. 8)
|
||||||
|
* outputs: (none)
|
||||||
|
* modified: r0, r1, FPSCR
|
||||||
|
* notes:
|
||||||
|
* r0 and r1 will be truncated before fitting into FPSCR
|
||||||
|
*/
|
||||||
|
reconfig:
|
||||||
|
push {r0-r2}
|
||||||
|
and r0, r0, 3
|
||||||
|
eor r0, r0, 1
|
||||||
|
sub r1, r1, 1
|
||||||
|
and r1, r1, 7
|
||||||
|
mov r0, r0, lsl 20
|
||||||
|
orr r0, r0, r1, lsl 16
|
||||||
|
vmrs r2, fpscr
|
||||||
|
bic r2, 55*65536
|
||||||
|
orr r2, r2, r0
|
||||||
|
vmsr fpscr, r0
|
||||||
|
pop {r0-r2}
|
||||||
|
bx lr
|
||||||
|
|
||||||
|
deconfig:
|
||||||
|
push {r0, r1, lr}
|
||||||
|
mov r0, 1
|
||||||
|
mov r1, 1
|
||||||
|
bl reconfig
|
||||||
|
pop {r0, r1, pc}
|
||||||
28
userland/arch/common.h
Normal file
28
userland/arch/common.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef COMMON_H
|
||||||
|
#define COMMON_H
|
||||||
|
|
||||||
|
/* We define in this header only macros that are the same on all archs. */
|
||||||
|
|
||||||
|
/* common_arch.h contains arch specific macros. */
|
||||||
|
#include "common_arch.h"
|
||||||
|
|
||||||
|
.extern \
|
||||||
|
exit, \
|
||||||
|
printf, \
|
||||||
|
puts \
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Assert that the given branch instruction is taken. */
|
||||||
|
#define ASSERT(branch_if_pass) \
|
||||||
|
branch_if_pass 1f; \
|
||||||
|
FAIL; \
|
||||||
|
1: \
|
||||||
|
;
|
||||||
|
|
||||||
|
/* Assert that a register equals another register. */
|
||||||
|
#define ASSERT_EQ_REG(reg1, reg2) \
|
||||||
|
cmp reg1, reg2; \
|
||||||
|
ASSERT(beq); \
|
||||||
|
;
|
||||||
|
|
||||||
|
#endif
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user