Chinese LoongArch Architecture Evaluation (Part 1 of 3)
October 2nd, 2025 by Brian
The following is a multi-part series on our evaluation of the Chinese LoongArch Architecture. This series summarizes work conducted by one of our interns, Neven J., over the course of the summer.
The Chinese LoongArch architecture, developed by Loongson, is a homegrown instruction set architecture (ISA) intended to reduce reliance on foreign technologies like x86 and ARM. It supports features common to modern processors, including out-of-order and speculative execution, which improve performance but also introduce the same class of vulnerabilities that have affected Intel, AMD, and ARM processors (e.g., Spectre, Meltdown). Analyzing LoongArch for speculative execution attacks is important because these microarchitectural flaws often transcend ISA boundaries and arise from shared design choices in modern CPUs. Understanding whether LoongArch inherits similar weaknesses helps us evaluate its security posture, identify potential side channels, and determine if mitigations are necessary to protect sensitive data in systems built on this architecture.
Initial Findings Upon Receiving the Dev Board
Old World vs. New World
A variety of basic information about the dev board was collected. Perhaps the most significant finding is that the dev board is running Linux version 4.19.190+, despite the fact that support for LoongArch wasn’t added to the official Linux kernel until version 5.19. Additionally, the dev board is running version 2.28 of glibc, which also didn’t officially get LoongArch support until version 2.36. Furthermore, running /lib/libc.so.6 revealed that it was compiled with gcc version 8.3.0. Once again, gcc did not get LoongArch support until version 12.1. All of these signs indicate that our system is in the “Old World,” which is comprised of the internal, pre-public-release versions of software such as Linux, glibc, gcc, etc. used by Loongson.
When initially creating a toolchain for cross-compiling from x86 to LoongArch, we used the readily available versions of gcc and glibc that have official support for LoongArch. However, this caused conflicts, particularly when trying to dynamically link to any libraries already present on the dev board. Hence, we initially developed our software with statically-linked glibc so that it could actually be executed on the dev board. However, since then, we came across this GitHub repo, which provided us with an old-world toolchain. This enabled us to build programs that could make use of dynamic linking on the dev board. Additionally, we found this repo that appears to contain the source code for Loongson’s internal Old World version of Linux.
Dev Board Oddities
Aside from the above software versioning issues, there were other quirks discovered during the initial analysis of the dev board. For instance, running cat /proc/cpuinfo yielded
system type : generic-loongson-machine
processor : 0
package : 0
core : 0
cpu family : Loongson-64bit
model name :
CPU Revision : 0x00
FPU Revision : 0x00
CPU MHz : 1000.00
BogoMIPS : 2000.00
TLB entries : 64
Address sizes : 40 bits physical, 40 bits virtual
isa : loongarch32 loongarch64
features : cpucfg lam fpu lsx lbt_mips
hardware watchpoint : yes, iwatch count: 4, dwatch count: 2
processor : 1
package : 0
core : 1
cpu family : Loongson-64bit
model name :
CPU Revision : 0x00
FPU Revision : 0x00
CPU MHz : 1000.00
BogoMIPS : 2000.00
TLB entries : 64
Address sizes : 40 bits physical, 40 bits virtual
isa : loongarch32 loongarch64
features : cpucfg lam fpu lsx lbt_mips
hardware watchpoint : yes, iwatch count: 4, dwatch count: 2Notably, the system type is listed as generic-loongson-machine and there is no model name present. Further adding to this confusion, the hostname on the dev board was set to ls3a5000, the model name of one of Loongson’s newer CPUs. However, the actual CPU on the board is clearly not the ls3a5000, as the dev board only has 2 cores but the ls3a5000 has 4. According to the text silkscreened on the dev board, it uses the ls2k1000 (specifically the ls2k1000LA, the LoongArch version of this processor), which aligns with the hardware info found in /proc/cpuinfo.
Bootloader Investigations
Much of the software found on the dev board appears to be internal Loongson versions that are part of the “Old World.” In order to ease the development process for the dev board, we hoped to update the OS to a New World distribution that has official support for LoongArch. However, we were unable to boot a simple USB with a modern installation image.
First, to allow the bootloader to automatically detect the kernel on the USB, the following boot.cfg file was added to the USB following the documentation found here (after translating to English):
Default 0
Showmenu 1
Title Fedora (USB)
kernel (usb0,0)/boot/vmlinuz0
initrd (usb0,0)/boot/initrd0.img
args root=live rootfstype=auto ro rd.live.image quiet splashThis file simply tells the bootloader where to find the kernel image and the initrd image. With this file added, the bootloader showed a menu entry with the options specified in the boot.cfg file. However, when attempting to boot from the USB, the following error was produced:
Now booting the <Title>
Loading file: <path to linux kernel>
<path to linux kernel>: boot failed
Configuration failed.
Press any key to continue ...As indicated here, it seems that the dev board’s bootloader is expecting an ELF format kernel image, whereas the New World kernels are EFI applications. Using this tool, the ELF was able to be extracted from the kernel image on one of ISO files. The ISO file was then repacked with mkisofs, flashed onto the USB drive, and a new boot.cfg file was used to boot the kernel ELF file. The bootloader actually was able to recognize this file, and it loaded it very slowly. However, the system still failed to boot.
Flashing a Loongnix ISO from here (which is an Old World distribution where the kernel is an ELF) allowed the kernel image to be loaded, but there were graphical issues when it was run.
Additionally, the Debian port page says
You MUST update the board EFI to the newest version before install Debian
and provides this resource for getting firmware images, but all of these images seem to be for newer Loongson products than our dev board.
Overall, it seems that our system does not have UEFI support and thus cannot boot New World distributions. This page says
If your system does not support UEFI or you don't want to use it, you'll need to figure out how to configure the booting process of the system on your own.
Digging Deeper
The bootloader/firmware installed on the dev board provides an about command. When executed, it indicates that documentation can be found at http://www.pmon2000.com, but this website no longer exists, although documentation can be found on the Internet Archive here. Overall, it seems that the firmware in use is PMON 2000, which dates back to the early 2000s. Some (Chinese) documentation for Loongson’s version of PMON can be found here. It also seems (per here) that Loongson used PMON with their earlier MIPS-based processors.
Before updating the firmware, first an effort was made to pull the existing firmware off the board as a backup. Following this documentation (which although it is intended for a different Loongson CPU seems to hold true for ours), reading 1 MB of memory starting at physical address 0x1fc00000 extracted the firmware from the board. Reading an extra megabyte both above and below this chunk yielded all 0s, confirming that the firmware file fit into this 1 MB chunk. Using binwalk revealed that it contained gzip-compressed data by the name of pmon.bin. A little more digging online revealed this GitHub repo, which seems to contain the source code for the version of PMON used on our board.
Updating the firmware was eventually deemed too risky, as appropriate firmware could not easily be found (as mentioned early, the Loongson firmware updating GitHub repo only provides firmware for newer boards). Although a U-Boot binary was found here that seemed to be appropriate for the dev board, it still seemed too risky to attempt updating the firmware, especially considering that we only have one dev board.
Bootloader Shim
An alternative to updating the board’s firmware would be to have the bootloader load another program (a “shim”) that would then go on to load either Grub or a newer Linux kernel directly. The PMON repo has some simple example programs that served as a starting point. These programs’ main functions received a pointer to the following callvectors struct as their fourth argument:
struct callvectors {
int (*open) (char *, int, int);
int (*close) (int);
int (*read) (int, void *, int);
int (*write) (int, void *, int);
off_t (*lseek) (int, off_t, int);
int (*printf) (const char *, ...);
void (*cacheflush) (void);
char* (*gets) (char *);
};With this, a very minimal ELF executable was created that simply used these provided function pointers for basic file IO. However, to make a fully-functional shim, it seemed necessary to have other functions, including things like malloc. Finding the addresses of these functions was non-trivial: Ghidra was not able to identify any of the functions in the pmon.bin file from before by name. Compiling another version of PMON from the source code found produced a binary with different function alignment (each function was 16-byte aligned) so the offsets throughout the file were completely off (using -falign-functions didn’t seem to resolve this issue). Thus, to find functions, the original firmware file was searched for byte sequences found in the newly compiled binary (that was not stripped), and manual analysis was used to match functions.
Ultimately, the shim was able to successfully chain load the U-Boot binary found earlier, but unfortunately, this bootloader also does not seem to support EFI images. It may be possible to create an appropriate image for a New World distribution that can be loaded by U-Boot.
The next step is to find hidden or buggy instructions in this architecture.
