| /**************************************************************************** |
| * apps/examples/fdpicxip/fdpicxip_main.c |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * End to end demonstration of the FDPIC module loader over a writable |
| * execute-in-place file system: |
| * |
| * 1. write a module into xipfs at run time, the way a download would, |
| * 2. confirm the file system can hand out a direct flash pointer for it, |
| * 3. run it, in some cases as two concurrent instances, |
| * 4. confirm the shared text was pinned in place while they ran and |
| * released afterwards. |
| * |
| * The point of FDPIC here is that text stays in flash and only the writable |
| * segment is copied, once per running instance -- so a second instance, or |
| * a second module sharing a library, costs data and no code. |
| * |
| * This demonstrates; it does not assert. The assertions live in the fdpic |
| * and reject sections of apps/testing/fs/xipfs. |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| |
| #include <elf.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <spawn.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <syslog.h> |
| #include <unistd.h> |
| |
| #include <nuttx/fs/ioctl.h> |
| #include <nuttx/fs/xipfs.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| #define MOUNTPT CONFIG_EXAMPLES_FDPICXIP_MOUNTPT |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| /* The modules in modules/, which mkblobs.sh embeds after they are linked */ |
| |
| extern const unsigned char g_fdpicxip_qsorter[]; |
| extern const unsigned int g_fdpicxip_qsorter_len; |
| extern const unsigned char g_fdpicxip_libcounter_so[]; |
| extern const unsigned int g_fdpicxip_libcounter_so_len; |
| extern const unsigned char g_fdpicxip_user[]; |
| extern const unsigned int g_fdpicxip_user_len; |
| extern const unsigned char g_fdpicxip_libshape_so[]; |
| extern const unsigned int g_fdpicxip_libshape_so_len; |
| extern const unsigned char g_fdpicxip_cxxuser[]; |
| extern const unsigned int g_fdpicxip_cxxuser_len; |
| extern const unsigned char g_fdpicxip_lazymod[]; |
| extern const unsigned int g_fdpicxip_lazymod_len; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: extent_info_path |
| * |
| * Description: |
| * Where a file physically lies: its extent, its flash address, and how |
| * many live mappings are pinning it in place. |
| * |
| ****************************************************************************/ |
| |
| static int extent_info_path(FAR const char *path, |
| FAR struct xipfs_extent_info_s *info) |
| { |
| int fd; |
| int ret; |
| |
| fd = open(path, O_RDONLY | O_CLOEXEC); |
| if (fd < 0) |
| { |
| return -errno; |
| } |
| |
| ret = ioctl(fd, XIPFSIOC_EXTENTINFO, (unsigned long)(uintptr_t)info); |
| close(fd); |
| |
| return ret < 0 ? -errno : 0; |
| } |
| |
| /**************************************************************************** |
| * Shared library demonstration |
| * |
| * Stages a library and a module that needs it, then runs two instances. |
| * Both share one mapped copy of each object's code, executed in place from |
| * flash. Each instance gets its own copy of the module's data, because |
| * exec() loads the module afresh; the library is opened with dlopen() and |
| * so there is one of it, data included. The totals therefore interleave, |
| * and each instance checks only that its own adds all landed. |
| ****************************************************************************/ |
| |
| static int stage_blob(FAR const char *path, FAR const unsigned char *data, |
| unsigned int len) |
| { |
| ssize_t n; |
| int fd; |
| |
| unlink(path); |
| |
| fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0755); |
| if (fd < 0) |
| { |
| return -errno; |
| } |
| |
| if (ftruncate(fd, len) < 0) |
| { |
| close(fd); |
| return -errno; |
| } |
| |
| n = write(fd, data, len); |
| close(fd); |
| |
| return (n == (ssize_t)len) ? 0 : -EIO; |
| } |
| |
| /**************************************************************************** |
| * Name: qsorter_main |
| * |
| * Description: |
| * The simplest case the loader has: one self-contained FDPIC module, no |
| * library behind it, run as two concurrent instances. It is the FDPIC |
| * counterpart of what this demo does by default with NXFLAT, and the |
| * only subcommand that exercises a module with no second object -- solib |
| * and cxx both bring a library, jmprel is about how imports are bound. |
| * |
| * The two instances overlap deliberately: each fills its array, sleeps, |
| * then sorts. One shared copy of the text, a private copy of the data |
| * per instance -- shared data would show up as corrupted output, and the |
| * pin count while both are alive is what stops the defragmenter |
| * relocating code that is executing. |
| * |
| ****************************************************************************/ |
| |
| static int qsorter_main(void) |
| { |
| struct xipfs_extent_info_s info; |
| FAR char *args[3]; |
| pid_t pid[2]; |
| char seed[8]; |
| int status; |
| int ret; |
| int i; |
| |
| syslog(LOG_INFO, |
| "\n=== FDPIC module executed in place from xipfs ===\n\n"); |
| |
| ret = stage_blob(MOUNTPT "/qsorter", g_fdpicxip_qsorter, |
| g_fdpicxip_qsorter_len); |
| if (ret < 0) |
| { |
| syslog(LOG_INFO, "staging the module failed: %d\n", ret); |
| return EXIT_FAILURE; |
| } |
| |
| syslog(LOG_INFO, "staged qsorter (%u bytes)\n", g_fdpicxip_qsorter_len); |
| |
| if (extent_info_path(MOUNTPT "/qsorter", &info) == 0) |
| { |
| syslog(LOG_INFO, "extent: block %" PRIu32 " x%" PRIu32 ", size %" |
| PRIu32 ", flash addr 0x%08lx\n", |
| info.start_block, info.nblocks, info.size, |
| (unsigned long)info.xipaddr); |
| |
| if (info.xipaddr == 0) |
| { |
| syslog(LOG_INFO, |
| "ERROR: filesystem cannot expose a flash pointer; the " |
| "module would have to be copied to RAM\n"); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| syslog(LOG_INFO, "\nspawning two concurrent instances...\n\n"); |
| |
| for (i = 0; i < 2; i++) |
| { |
| snprintf(seed, sizeof(seed), "%d", i + 1); |
| args[0] = (FAR char *)"qsorter"; |
| args[1] = seed; |
| args[2] = NULL; |
| |
| ret = posix_spawn(&pid[i], MOUNTPT "/qsorter", NULL, NULL, args, NULL); |
| if (ret != 0) |
| { |
| syslog(LOG_INFO, "spawn %d failed: %d\n", i + 1, ret); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| usleep(150000); |
| |
| if (extent_info_path(MOUNTPT "/qsorter", &info) == 0) |
| { |
| syslog(LOG_INFO, "[while running] pins on the shared text = %lu\n\n", |
| (unsigned long)info.pincount); |
| } |
| |
| for (i = 0; i < 2; i++) |
| { |
| waitpid(pid[i], &status, 0); |
| } |
| |
| usleep(100000); |
| |
| if (extent_info_path(MOUNTPT "/qsorter", &info) == 0) |
| { |
| syslog(LOG_INFO, "\n[after exit] pins on the shared text = %lu\n", |
| (unsigned long)info.pincount); |
| } |
| |
| syslog(LOG_INFO, "\n=== done ===\n"); |
| return EXIT_SUCCESS; |
| } |
| |
| static int solib_main(void) |
| { |
| struct xipfs_extent_info_s li; |
| struct xipfs_extent_info_s ui; |
| FAR char *args[3]; |
| pid_t pid[2]; |
| char seed[8]; |
| int status; |
| int ret; |
| int i; |
| |
| syslog(LOG_INFO, "\n=== FDPIC shared library, executed in place ===\n\n"); |
| |
| ret = stage_blob(MOUNTPT "/libcounter.so", g_fdpicxip_libcounter_so, |
| g_fdpicxip_libcounter_so_len); |
| if (ret < 0) |
| { |
| syslog(LOG_INFO, "staging the library failed: %d\n", ret); |
| return EXIT_FAILURE; |
| } |
| |
| ret = stage_blob(MOUNTPT "/user", g_fdpicxip_user, g_fdpicxip_user_len); |
| if (ret < 0) |
| { |
| syslog(LOG_INFO, "staging the module failed: %d\n", ret); |
| return EXIT_FAILURE; |
| } |
| |
| syslog(LOG_INFO, "staged libcounter.so (%u bytes) and user (%u bytes)\n", |
| g_fdpicxip_libcounter_so_len, g_fdpicxip_user_len); |
| |
| if (extent_info_path(MOUNTPT "/libcounter.so", &li) == 0 && |
| extent_info_path(MOUNTPT "/user", &ui) == 0) |
| { |
| syslog(LOG_INFO, " libcounter.so text at 0x%08lx\n", |
| (unsigned long)li.xipaddr); |
| syslog(LOG_INFO, " user text at 0x%08lx\n", |
| (unsigned long)ui.xipaddr); |
| } |
| |
| syslog(LOG_INFO, "\nspawning two instances, each bumping by its own" |
| " seed...\n\n"); |
| |
| for (i = 0; i < 2; i++) |
| { |
| snprintf(seed, sizeof(seed), "%d", i + 1); |
| args[0] = (FAR char *)"user"; |
| args[1] = seed; |
| args[2] = NULL; |
| |
| ret = posix_spawn(&pid[i], MOUNTPT "/user", NULL, NULL, args, NULL); |
| if (ret != 0) |
| { |
| syslog(LOG_INFO, "spawn %d failed: %d\n", i + 1, ret); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| usleep(150000); |
| |
| if (extent_info_path(MOUNTPT "/libcounter.so", &li) == 0) |
| { |
| syslog(LOG_INFO, "[while running] pins on the library's shared text" |
| " = %lu\n\n", (unsigned long)li.pincount); |
| } |
| |
| for (i = 0; i < 2; i++) |
| { |
| waitpid(pid[i], &status, 0); |
| } |
| |
| usleep(100000); |
| |
| if (extent_info_path(MOUNTPT "/libcounter.so", &li) == 0) |
| { |
| syslog(LOG_INFO, "\n[after exit] pins on the library's shared text" |
| " = %lu\n", (unsigned long)li.pincount); |
| } |
| |
| syslog(LOG_INFO, "\n=== done ===\n"); |
| return EXIT_SUCCESS; |
| } |
| |
| /**************************************************************************** |
| * Name: cxx_main |
| * |
| * Description: |
| * The same shape as solib_main(), in C++, and with one thing extra to |
| * prove: that global constructors ran. |
| * |
| * That is worth a demo of its own because of how it fails. A loader that |
| * ignores DT_INIT_ARRAY still loads a C++ module, still resolves every |
| * symbol, and still runs it -- the globals are simply left as .bss and |
| * every field reads zero. Nothing faults and nothing is logged; the |
| * module just answers wrong. So both objects here carry a magic number |
| * that only a constructor writes, and the module reports whether it found |
| * them. |
| * |
| * The ordering is checked too. The module's constructor samples whether |
| * the library was already constructed when it ran, which is the only |
| * moment the answer is still interesting -- by the time main() is entered |
| * both have run either way. |
| * |
| ****************************************************************************/ |
| |
| static int cxx_main(void) |
| { |
| struct xipfs_extent_info_s li; |
| struct xipfs_extent_info_s ui; |
| FAR char *args[3]; |
| pid_t pid[2]; |
| char seed[8]; |
| int status; |
| int ret; |
| int i; |
| |
| syslog(LOG_INFO, "\n=== C++ module and shared library, executed in" |
| " place ===\n\n"); |
| |
| ret = stage_blob(MOUNTPT "/libshape.so", g_fdpicxip_libshape_so, |
| g_fdpicxip_libshape_so_len); |
| if (ret < 0) |
| { |
| syslog(LOG_INFO, "staging the library failed: %d\n", ret); |
| return EXIT_FAILURE; |
| } |
| |
| ret = stage_blob(MOUNTPT "/cxxuser", g_fdpicxip_cxxuser, |
| g_fdpicxip_cxxuser_len); |
| if (ret < 0) |
| { |
| syslog(LOG_INFO, "staging the module failed: %d\n", ret); |
| return EXIT_FAILURE; |
| } |
| |
| syslog(LOG_INFO, "staged libshape.so (%u bytes) and cxxuser (%u bytes)\n", |
| g_fdpicxip_libshape_so_len, g_fdpicxip_cxxuser_len); |
| |
| if (extent_info_path(MOUNTPT "/libshape.so", &li) == 0 && |
| extent_info_path(MOUNTPT "/cxxuser", &ui) == 0) |
| { |
| syslog(LOG_INFO, " libshape.so text at 0x%08lx\n", |
| (unsigned long)li.xipaddr); |
| syslog(LOG_INFO, " cxxuser text at 0x%08lx\n", |
| (unsigned long)ui.xipaddr); |
| } |
| |
| syslog(LOG_INFO, "\nspawning two instances, each adding its own seed...\n" |
| "(each instance's constructors run at load time, before main)\n\n"); |
| |
| for (i = 0; i < 2; i++) |
| { |
| snprintf(seed, sizeof(seed), "%d", i + 1); |
| args[0] = (FAR char *)"cxxuser"; |
| args[1] = seed; |
| args[2] = NULL; |
| |
| ret = posix_spawn(&pid[i], MOUNTPT "/cxxuser", NULL, NULL, args, NULL); |
| if (ret != 0) |
| { |
| syslog(LOG_INFO, "spawn %d failed: %d\n", i + 1, ret); |
| return EXIT_FAILURE; |
| } |
| } |
| |
| usleep(150000); |
| |
| if (extent_info_path(MOUNTPT "/libshape.so", &li) == 0) |
| { |
| syslog(LOG_INFO, "[while running] pins on the library's shared text" |
| " = %lu\n\n", (unsigned long)li.pincount); |
| } |
| |
| /* Only a barrier. The exit status is deliberately not read: waitpid() |
| * retains it for an already-exited child only under |
| * CONFIG_SCHED_CHILD_STATUS, and both instances run for the same time, so |
| * the second is always gone by the time the first wait returns. Checking |
| * `status` after a failed wait reads whatever the previous iteration left |
| * there, which looks like a pass. The suite in apps/testing/fs/xipfs |
| * asserts the real result via files; this demo reports through the log. |
| */ |
| |
| ret = EXIT_SUCCESS; |
| |
| for (i = 0; i < 2; i++) |
| { |
| waitpid(pid[i], &status, 0); |
| } |
| |
| /* The destructors run on unload, which happens as each task is reaped -- |
| * after waitpid() has returned, so give them a moment to be logged. |
| */ |
| |
| usleep(100000); |
| |
| if (extent_info_path(MOUNTPT "/libshape.so", &li) == 0) |
| { |
| syslog(LOG_INFO, "\n[after exit] pins on the library's shared text" |
| " = %lu\n", (unsigned long)li.pincount); |
| } |
| |
| syslog(LOG_INFO, "\n=== %s ===\n", |
| ret == EXIT_SUCCESS ? "done" : "FAILED"); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: jmprel_main |
| * |
| * Description: |
| * Load a module whose imports the linker put in DT_JMPREL. |
| * |
| * Every other module here is linked with -z now, which leaves the imported |
| * descriptors in DT_REL. This one empties BINDNOW in modules/Makefile, so |
| * it has no DT_REL at all -- both of its imports are in the PLT relocation |
| * table. |
| * |
| * There is no lazy resolver in the loader, so a DT_JMPREL table that went |
| * unwalked was not deferred work: the descriptors stayed unrelocated and |
| * the module's first call into the firmware branched to whatever the |
| * unrelocated word held. It loaded cleanly and printed nothing, then |
| * took an INVSTATE UsageFault that escalated to a HardFault -- no |
| * console, no crash dump. If this demo prints its line, both tables are |
| * being bound. |
| * |
| ****************************************************************************/ |
| |
| static int jmprel_main(void) |
| { |
| FAR char *args[3]; |
| pid_t pid; |
| int status; |
| int ret; |
| |
| syslog(LOG_INFO, "\n=== FDPIC module bound through DT_JMPREL ===\n\n"); |
| |
| ret = stage_blob(MOUNTPT "/lazymod", g_fdpicxip_lazymod, |
| g_fdpicxip_lazymod_len); |
| if (ret < 0) |
| { |
| syslog(LOG_INFO, "staging the module failed: %d\n", ret); |
| return EXIT_FAILURE; |
| } |
| |
| syslog(LOG_INFO, "staged lazymod (%u bytes), all imports in DT_JMPREL\n\n", |
| g_fdpicxip_lazymod_len); |
| |
| args[0] = (FAR char *)"lazymod"; |
| args[1] = (FAR char *)"42"; |
| args[2] = NULL; |
| |
| ret = posix_spawn(&pid, MOUNTPT "/lazymod", NULL, NULL, args, NULL); |
| if (ret != 0) |
| { |
| syslog(LOG_INFO, "spawn failed: %d\n", ret); |
| return EXIT_FAILURE; |
| } |
| |
| waitpid(pid, &status, 0); |
| |
| if (!WIFEXITED(status) || WEXITSTATUS(status) != EXIT_SUCCESS) |
| { |
| syslog(LOG_INFO, "\n=== FAILED: module did not exit cleanly ===\n"); |
| return EXIT_FAILURE; |
| } |
| |
| syslog(LOG_INFO, "\n=== done: the module reached the firmware and came" |
| " back ===\n"); |
| return EXIT_SUCCESS; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| int main(int argc, FAR char *argv[]) |
| { |
| if (argc > 1 && strcmp(argv[1], "solib") == 0) |
| { |
| return solib_main(); |
| } |
| |
| if (argc > 1 && strcmp(argv[1], "cxx") == 0) |
| { |
| return cxx_main(); |
| } |
| |
| if (argc > 1 && strcmp(argv[1], "jmprel") == 0) |
| { |
| return jmprel_main(); |
| } |
| |
| if (argc > 1 && strcmp(argv[1], "qsort") != 0) |
| { |
| fprintf(stderr, "usage: fdpicxip [qsort|solib|cxx|jmprel]\n"); |
| return EXIT_FAILURE; |
| } |
| |
| return qsorter_main(); |
| } |