mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2026-01-23 02:05:57 +01:00
baremetal: symlink all programs that currently run on both userland and baremetal
This commit is contained in:
74
README.adoc
74
README.adoc
@@ -755,7 +755,7 @@ Or to run a baremetal example instead:
|
||||
....
|
||||
./run \
|
||||
--arch aarch64 \
|
||||
--baremetal baremetal/hello.c \
|
||||
--baremetal baremetal/c/hello.c \
|
||||
--qemu-which host \
|
||||
;
|
||||
....
|
||||
@@ -1193,11 +1193,11 @@ Our C bare-metal compiler is built with link:https://github.com/crosstool-ng/cro
|
||||
|
||||
Every `.c` file inside link:baremetal/[] and `.S` file inside `baremetal/arch/<arch>/` generates a separate baremetal image.
|
||||
|
||||
For example, to run link:baremetal/hello.c[] in QEMU do:
|
||||
For example, to run link:baremetal/c/hello.c[] in QEMU do:
|
||||
|
||||
....
|
||||
./build --arch aarch64 --download-dependencies qemu-baremetal
|
||||
./run --arch aarch64 --baremetal baremetal/hello.c
|
||||
./run --arch aarch64 --baremetal baremetal/c/hello.c
|
||||
....
|
||||
|
||||
The terminal prints:
|
||||
@@ -1238,14 +1238,14 @@ echo $?
|
||||
To modify a baremetal program, simply edit the file, e.g.
|
||||
|
||||
....
|
||||
vim baremetal/hello.c
|
||||
vim baremetal/c/hello.c
|
||||
....
|
||||
|
||||
and rebuild:
|
||||
|
||||
....
|
||||
./build-baremetal --arch aarch64
|
||||
./run --arch aarch64 --baremetal baremetal/hello.c
|
||||
./run --arch aarch64 --baremetal baremetal/c/hello.c
|
||||
....
|
||||
|
||||
`./build qemu-baremetal` that we run previously is only needed for the initial build. That script calls link:build-baremetal[] for us, in addition to building prerequisites such as QEMU and crosstool-NG.
|
||||
@@ -1255,7 +1255,7 @@ and rebuild:
|
||||
Alternatively, for the sake of tab completion, we also accept relative paths inside `baremetal/`, for example the following also work:
|
||||
|
||||
....
|
||||
./run --arch aarch64 --baremetal baremetal/hello.c
|
||||
./run --arch aarch64 --baremetal baremetal/c/hello.c
|
||||
./run --arch aarch64 --baremetal baremetal/arch/aarch64/add.S
|
||||
....
|
||||
|
||||
@@ -1269,7 +1269,7 @@ To use gem5 instead of QEMU do:
|
||||
|
||||
....
|
||||
./build --download-dependencies gem5-baremetal
|
||||
./run --arch aarch64 --baremetal baremetal/hello.c --emulator gem5
|
||||
./run --arch aarch64 --baremetal baremetal/c/hello.c --emulator gem5
|
||||
....
|
||||
|
||||
and then <<qemu-buildroot-setup,as usual>> open a shell with:
|
||||
@@ -1281,7 +1281,7 @@ and then <<qemu-buildroot-setup,as usual>> open a shell with:
|
||||
Or as usual, <<tmux>> users can do both in one go with:
|
||||
|
||||
....
|
||||
./run --arch aarch64 --baremetal baremetal/hello.c --emulator gem5 --tmux
|
||||
./run --arch aarch64 --baremetal baremetal/c/hello.c --emulator gem5 --tmux
|
||||
....
|
||||
|
||||
TODO: the carriage returns are a bit different than in QEMU, see: <<gem5-baremetal-carriage-return>>.
|
||||
@@ -1289,8 +1289,8 @@ TODO: the carriage returns are a bit different than in QEMU, see: <<gem5-baremet
|
||||
Note that `./build-baremetal` requires the `--emulator gem5` option, and generates separate executable images for both, as can be seen from:
|
||||
|
||||
....
|
||||
echo "$(./getvar --arch aarch64 --baremetal baremetal/hello.c --emulator qemu image)"
|
||||
echo "$(./getvar --arch aarch64 --baremetal baremetal/hello.c --emulator gem5 image)"
|
||||
echo "$(./getvar --arch aarch64 --baremetal baremetal/c/hello.c --emulator qemu image)"
|
||||
echo "$(./getvar --arch aarch64 --baremetal baremetal/c/hello.c --emulator gem5 image)"
|
||||
....
|
||||
|
||||
This is unlike the Linux kernel that has a single image for both QEMU and gem5:
|
||||
@@ -1306,14 +1306,14 @@ The reason for that is that on baremetal we don't parse the <<device-tree,device
|
||||
|
||||
....
|
||||
./build-baremetal --arch aarch64 --emulator gem5 --machine RealViewPBX
|
||||
./run --arch aarch64 --baremetal baremetal/hello.c --emulator gem5 --machine RealViewPBX
|
||||
./run --arch aarch64 --baremetal baremetal/c/hello.c --emulator gem5 --machine RealViewPBX
|
||||
....
|
||||
|
||||
This generates yet new separate images with new magic constants:
|
||||
|
||||
....
|
||||
echo "$(./getvar --arch aarch64 --baremetal baremetal/hello.c --emulator gem5 --machine VExpress_GEM5_V1 image)"
|
||||
echo "$(./getvar --arch aarch64 --baremetal baremetal/hello.c --emulator gem5 --machine RealViewPBX image)"
|
||||
echo "$(./getvar --arch aarch64 --baremetal baremetal/c/hello.c --emulator gem5 --machine VExpress_GEM5_V1 image)"
|
||||
echo "$(./getvar --arch aarch64 --baremetal baremetal/c/hello.c --emulator gem5 --machine RealViewPBX image)"
|
||||
....
|
||||
|
||||
But just stick to newer and better `VExpress_GEM5_V1` unless you have a good reason to use `RealViewPBX`.
|
||||
@@ -12685,7 +12685,7 @@ Guaranteed undefined! Therefore raise illegal instruction signal. Used by GCC `_
|
||||
* link:userland/arch/arm/udf.S[]
|
||||
* link:userland/arch/aarch64/udf.S[]
|
||||
|
||||
TODO: why GNU GAS 2.29 does not have a mnemonic for it?
|
||||
Why GNU GAS 2.29 does not have a mnemonic for it in A64 because it is very recent: shows in [[armarm8-db]] but not `ca`.
|
||||
|
||||
=== ARM SIMD
|
||||
|
||||
@@ -12932,12 +12932,30 @@ https://static.docs.arm.com/ddi0487/ca/DDI0487C_a_armv8_arm.pdf
|
||||
|
||||
Latest version: https://developer.arm.com/docs/ddi0487/latest/arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile
|
||||
|
||||
Versions are determined by two letteres in lexicographical order, e.g.:
|
||||
|
||||
* a
|
||||
* af
|
||||
* aj
|
||||
* aj
|
||||
* b
|
||||
* ba
|
||||
* bb
|
||||
* ca
|
||||
|
||||
The link: https://static.docs.arm.com/ddi0487/ca/DDI0487C_a_armv8_arm.pdf is the `ca` version for example.
|
||||
|
||||
The official comprehensive ARMv8 reference.
|
||||
|
||||
ISA quick references can be found in some places:
|
||||
|
||||
* https://web.archive.org/web/20161009122630/http://infocenter.arm.com/help/topic/com.arm.doc.qrc0001m/QRC0001_UAL.pdf
|
||||
|
||||
[[armarm8-db]]
|
||||
===== ARMv8 architecture reference manual db
|
||||
|
||||
https://static.docs.arm.com/ddi0487/db/DDI0487D_b_armv8_arm.pdf
|
||||
|
||||
[[armv8-programmers-guide]]
|
||||
===== Programmer's Guide for ARMv8-A
|
||||
|
||||
@@ -12962,19 +12980,19 @@ Except that is is even cooler here since we can easily control and understand ev
|
||||
For example, on the first shell:
|
||||
|
||||
....
|
||||
./run --arch arm --baremetal baremetal/hello.c --gdb-wait
|
||||
./run --arch arm --baremetal baremetal/c/hello.c --gdb-wait
|
||||
....
|
||||
|
||||
then on the second shell:
|
||||
|
||||
....
|
||||
./run-gdb --arch arm --baremetal baremetal/hello.c -- main
|
||||
./run-gdb --arch arm --baremetal baremetal/c/hello.c -- main
|
||||
....
|
||||
|
||||
Or if you are a <<tmux,tmux pro>>, do everything in one go with:
|
||||
|
||||
....
|
||||
./run --arch arm --baremetal baremetal/hello.c --gdb
|
||||
./run --arch arm --baremetal baremetal/c/hello.c --gdb
|
||||
....
|
||||
|
||||
Alternatively, to start from the very first executed instruction of our tiny <<baremetal-bootloaders>>:
|
||||
@@ -12982,13 +13000,13 @@ Alternatively, to start from the very first executed instruction of our tiny <<b
|
||||
....
|
||||
./run \
|
||||
--arch arm \
|
||||
--baremetal baremetal/hello.c \
|
||||
--baremetal baremetal/c/hello.c \
|
||||
--gdb-wait \
|
||||
--tmux-args=--no-continue \
|
||||
;
|
||||
....
|
||||
|
||||
Now you can just `stepi` to when jumping into main to go to the C code in link:baremetal/hello.c[].
|
||||
Now you can just `stepi` to when jumping into main to go to the C code in link:baremetal/c/hello.c[].
|
||||
|
||||
This is specially interesting for the executables that don't use the bootloader from under `baremetal/arch/<arch>/no_bootloader/*.S`, e.g.:
|
||||
|
||||
@@ -13125,7 +13143,7 @@ For `arm`, some baremetal examples compile fine with:
|
||||
....
|
||||
sudo apt-get install gcc-arm-none-eabi qemu-system-arm
|
||||
./build-baremetal --arch arm --gcc-which host-baremetal
|
||||
./run --arch arm --baremetal baremetal/hello.c --qemu-which host
|
||||
./run --arch arm --baremetal baremetal/c/hello.c --qemu-which host
|
||||
....
|
||||
|
||||
However, there are as usual limitations to using prebuilts:
|
||||
@@ -13154,7 +13172,7 @@ TODO: any advantage over QEMU? I doubt it, mostly using it as as toy for now:
|
||||
Without running `./run`, do directly:
|
||||
|
||||
....
|
||||
./run-gdb --arch arm --baremetal baremetal/hello.c --sim
|
||||
./run-gdb --arch arm --baremetal baremetal/c/hello.c --sim
|
||||
....
|
||||
|
||||
Then inside GDB:
|
||||
@@ -13720,12 +13738,12 @@ Source: link:test-baremetal[]
|
||||
Analogously to <<user-mode-tests>>, we can select individual tests or directories with:
|
||||
|
||||
....
|
||||
./test-baremetal --arch aarch64 baremetal/hello.c baremetal/arch/aarch64/no_bootloader/
|
||||
./test-baremetal --arch aarch64 baremetal/c/hello.c baremetal/arch/aarch64/no_bootloader/
|
||||
....
|
||||
|
||||
which would run all of:
|
||||
|
||||
* link:baremetal/hello.c[]
|
||||
* link:baremetal/c/hello.c[]
|
||||
* all tests under the directory: link:baremetal/arch/aarch64/no_bootloader/[]
|
||||
|
||||
We detect if tests failed by parsing logs for the <<magic-failure-string>>.
|
||||
@@ -15097,14 +15115,14 @@ Sources:
|
||||
If a test fails, re-run the test commands manually and use `--verbose` to understand what happened:
|
||||
|
||||
....
|
||||
./run --arch arm --background --baremetal baremetal/add.c --gdb-wait &
|
||||
./run-gdb --arch arm --baremetal baremetal/add.c --verbose -- main
|
||||
./run --arch arm --background --baremetal baremetal/c/add.c --gdb-wait &
|
||||
./run-gdb --arch arm --baremetal baremetal/c/add.c --verbose -- main
|
||||
....
|
||||
|
||||
and possibly repeat the GDB steps manually with the usual:
|
||||
|
||||
....
|
||||
./run-gdb --arch arm --baremetal baremetal/add.c --no-continue --verbose
|
||||
./run-gdb --arch arm --baremetal baremetal/c/add.c --no-continue --verbose
|
||||
....
|
||||
|
||||
To debug GDB problems on gem5, you might want to enable the following <<gem5-tracing,tracing>> options:
|
||||
@@ -15112,7 +15130,7 @@ To debug GDB problems on gem5, you might want to enable the following <<gem5-tra
|
||||
....
|
||||
./run \
|
||||
--arch arm \
|
||||
--baremetal baremetal/add.c \
|
||||
--baremetal baremetal/c/add.c \
|
||||
--gdb-wait \
|
||||
--trace GDBRecv,GDBSend \
|
||||
--trace-stdout \
|
||||
@@ -15163,7 +15181,7 @@ So setup this `on_exit` automatically from all our <<baremetal-bootloaders>>, so
|
||||
+
|
||||
The following examples end up testing that our setup is working:
|
||||
+
|
||||
* link:baremetal/assert_fail.c[]
|
||||
* link:baremetal/c/assert_fail.c[]
|
||||
* link:baremetal/return1.c[]
|
||||
* link:baremetal/return2.c[]
|
||||
* link:baremetal/exit0.c[]
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../lkmc/add.c
|
||||
@@ -1 +0,0 @@
|
||||
../lkmc/add.py
|
||||
@@ -1 +0,0 @@
|
||||
../lkmc/assert_fail.c
|
||||
1
baremetal/c/add.c
Symbolic link
1
baremetal/c/add.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/add.c
|
||||
1
baremetal/c/add.py
Symbolic link
1
baremetal/c/add.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/add.py
|
||||
1
baremetal/c/assert_fail.c
Symbolic link
1
baremetal/c/assert_fail.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/assert_fail.c
|
||||
1
baremetal/c/hello.c
Symbolic link
1
baremetal/c/hello.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/hello.c
|
||||
1
baremetal/c/infinite_loop.c
Symbolic link
1
baremetal/c/infinite_loop.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/infinite_loop.c
|
||||
1
baremetal/c/stderr.c
Symbolic link
1
baremetal/c/stderr.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/stderr.c
|
||||
@@ -1 +0,0 @@
|
||||
../lkmc/hello.c
|
||||
@@ -1,4 +0,0 @@
|
||||
int main(void) {
|
||||
while(1) {}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,22 +1,17 @@
|
||||
/* Test that input request through serial also works. */
|
||||
/* Test out of memory. */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
char c;
|
||||
char *ptr = NULL;
|
||||
size_t alloc_size = 1;
|
||||
while (1) {
|
||||
printf("enter a character\n");
|
||||
c = getchar();
|
||||
printf("got: %c\n", c);
|
||||
ptr = realloc(ptr, alloc_size);
|
||||
if (ptr == NULL) {
|
||||
puts("out of memory");
|
||||
break;
|
||||
} else {
|
||||
printf("new alloc of %zu bytes at address 0x%p\n", alloc_size, ptr);
|
||||
alloc_size <<= 1;
|
||||
}
|
||||
}
|
||||
6
bisect-gem5-gdb
Executable file
6
bisect-gem5-gdb
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# https://github.com/cirosantilli/linux-kernel-module-cheat#bisection
|
||||
set -eu
|
||||
cd ../..
|
||||
./build-gem5 --arch aarch64 --gem5-build-id bisect
|
||||
./test-gdb --arch aarch64 --quit-on-fail
|
||||
@@ -155,10 +155,14 @@ class LkmcCliFunction(cli_function.CliFunction):
|
||||
It would be beautiful to do this evaluation in a lazy way, e.g. with functions +
|
||||
cache decorators:
|
||||
https://stackoverflow.com/questions/815110/is-there-a-decorator-to-simply-cache-function-return-values
|
||||
|
||||
:param is_userland: on ./run, we detect if userland based on --userland. However, ./test-user-mode
|
||||
does not take --userland, and that causes problems.
|
||||
'''
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
is_userland=False,
|
||||
defaults=None,
|
||||
supported_archs=None,
|
||||
**kwargs
|
||||
@@ -171,6 +175,7 @@ class LkmcCliFunction(cli_function.CliFunction):
|
||||
kwargs['extra_config_params'] = os.path.basename(inspect.getfile(self.__class__))
|
||||
if defaults is None:
|
||||
defaults = {}
|
||||
self._is_userland = is_userland
|
||||
self._defaults = defaults
|
||||
self._is_common = True
|
||||
self._common_args = set()
|
||||
@@ -1199,7 +1204,7 @@ lunch aosp_{}-eng
|
||||
continue
|
||||
else:
|
||||
raise Exception('native emulator only supported in if target arch == host arch')
|
||||
if env['userland'] is None:
|
||||
if env['userland'] is None and not self._is_userland:
|
||||
if real_all_emulators:
|
||||
continue
|
||||
else:
|
||||
|
||||
21
lkmc/c/getchar.c
Normal file
21
lkmc/c/getchar.c
Normal file
@@ -0,0 +1,21 @@
|
||||
/* Get on character from stdin, and then print it back out.
|
||||
*
|
||||
* Same as getc(stdin).
|
||||
*
|
||||
* You have to press enter for the character to go through:
|
||||
* https://stackoverflow.com/questions/1798511/how-to-avoid-pressing-enter-with-getchar
|
||||
*
|
||||
* Used at:
|
||||
* https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1/53937376#53937376
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
char c;
|
||||
printf("enter a character: ");
|
||||
c = getchar();
|
||||
printf("you entered: %c\n", c);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
29
lkmc/c/infinite_loop.c
Normal file
29
lkmc/c/infinite_loop.c
Normal file
@@ -0,0 +1,29 @@
|
||||
/* Loop infinitely. Print an integer whenever a period is reached:
|
||||
*
|
||||
* ....
|
||||
* ./infinite_loop [period]
|
||||
* ....
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uintmax_t i, j, period;
|
||||
if (argc > 1) {
|
||||
period = strtoumax(argv[1], NULL, 10);
|
||||
} else {
|
||||
period = 100000000;
|
||||
}
|
||||
i = 0;
|
||||
j = 0;
|
||||
while (1) {
|
||||
i++;
|
||||
if (i % period == 0) {
|
||||
printf("%ju\n", j);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
lkmc/c/stderr.c
Normal file
7
lkmc/c/stderr.c
Normal file
@@ -0,0 +1,7 @@
|
||||
/* Print hello to stderr. */
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
fputs("hello\n", stderr);
|
||||
}
|
||||
@@ -258,9 +258,14 @@ path_properties_tuples = (
|
||||
)
|
||||
}
|
||||
),
|
||||
'c': (
|
||||
{},
|
||||
{
|
||||
'assert_fail.c': {'exit_status': 134},
|
||||
'exit1.c': {'exit_status': 1},
|
||||
'infinite_loop.c': {'more_than_1s': True},
|
||||
}
|
||||
),
|
||||
'exit1.c': {'exit_status': 1},
|
||||
'lib': {'no_executable': True},
|
||||
'getchar.c': {'interactive': True},
|
||||
'return1.c': {'exit_status': 1},
|
||||
|
||||
Submodule submodules/gem5 updated: 1bfc29b059...c4cc3145cd
@@ -17,7 +17,7 @@ TODO: expose all userland relevant ./run args here as well somehow.
|
||||
'''
|
||||
if not 'defaults' in kwargs:
|
||||
kwargs['defaults'] = {}
|
||||
super().__init__(*args, **kwargs)
|
||||
super().__init__(*args, is_userland=True, **kwargs)
|
||||
self.add_argument(
|
||||
'tests',
|
||||
nargs='*',
|
||||
|
||||
1
userland/c/add.c
Symbolic link
1
userland/c/add.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/add.c
|
||||
1
userland/c/add.py
Symbolic link
1
userland/c/add.py
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/add.py
|
||||
@@ -1 +1 @@
|
||||
../../lkmc/assert_fail.c
|
||||
../../lkmc/c/assert_fail.c
|
||||
@@ -1,21 +0,0 @@
|
||||
/* Get on character from stdin, and then print it back out.
|
||||
*
|
||||
* Same as getc(stdin).
|
||||
*
|
||||
* You have to press enter for the character to go through:
|
||||
* https://stackoverflow.com/questions/1798511/how-to-avoid-pressing-enter-with-getchar
|
||||
*
|
||||
* Used at:
|
||||
* https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1/53937376#53937376
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(void) {
|
||||
char c;
|
||||
printf("enter a character: ");
|
||||
c = getchar();
|
||||
printf("you entered: %c\n", c);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
1
userland/c/getchar.c
Symbolic link
1
userland/c/getchar.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/getchar.c
|
||||
@@ -1 +1 @@
|
||||
../../lkmc/hello.c
|
||||
../../lkmc/c/hello.c
|
||||
@@ -1,29 +0,0 @@
|
||||
/* Loop infinitely. Print an integer whenever a period is reached:
|
||||
*
|
||||
* ....
|
||||
* ./infinite_loop [period]
|
||||
* ....
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
uintmax_t i, j, period;
|
||||
if (argc > 1) {
|
||||
period = strtoumax(argv[1], NULL, 10);
|
||||
} else {
|
||||
period = 100000000;
|
||||
}
|
||||
i = 0;
|
||||
j = 0;
|
||||
while (1) {
|
||||
i++;
|
||||
if (i % period == 0) {
|
||||
printf("%ju\n", j);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
userland/c/infinite_loop.c
Symbolic link
1
userland/c/infinite_loop.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/infinite_loop.c
|
||||
@@ -1,7 +0,0 @@
|
||||
/* Print hello to stderr. */
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main(void) {
|
||||
fputs("hello\n", stderr);
|
||||
}
|
||||
1
userland/c/stderr.c
Symbolic link
1
userland/c/stderr.c
Symbolic link
@@ -0,0 +1 @@
|
||||
../../lkmc/c/stderr.c
|
||||
@@ -1 +0,0 @@
|
||||
Testing mostly infrastructure of this repository rather than anything else.
|
||||
@@ -1 +0,0 @@
|
||||
../../lkmc/add.c
|
||||
@@ -1 +0,0 @@
|
||||
../../lkmc/add.py
|
||||
Reference in New Issue
Block a user