mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
binfmt/fdpic: Add an FDPIC ELF module loader.
Lets a NOMMU target execute downloadable modules in place from memory-mapped NOR flash, so a module's text and rodata never consume RAM. It is the consumer of xipfs: the loader maps a module's read-only segment with MAP_XIP_STRICT, which resolves to a direct flash pointer or fails with -ENXIO rather than falling back to a RAM copy, and pins the extent for as long as the module is loaded so the defragmenter cannot relocate code that is executing. Only the writable segment is copied to RAM, once per running instance. The loader follows DT_NEEDED so a module can use shared libraries, each object getting its own GOT and its own data. FDPIC is what makes this possible: text is position-independent and each LOAD segment is placed independently, with the text-to-data offset communicated at load time through function descriptors and the GOT. A descriptor is a pair -- entry pointer plus data base -- so a module function handed back to the firmware carries the data base it needs. r9 holds that base at runtime, per the ARM FDPIC ABI. Reserving r9 across the base firmware is what allows a firmware routine to call back into module code and still arrive with the module's data base intact. Toolchain.defs puts --fixed-r9 in ARCHCPUFLAGS rather than CFLAGS, because almost every board Make.defs assigns CFLAGS with ':=' after including it, which would discard the flag; ARCHCPUFLAGS is re-expanded by that same assignment and so survives it. The CONFIG_PIC --fixed-r10 case is skipped under FDPIC, since reserving both registers would cost one for nothing. The DT_NEEDED walk is depth capped. fdpic_loaddepends() recursed once per link of a dependency chain with a path buffer on each frame and nothing to stop it, so a malformed module set overflowed the stack of whichever task called the loader instead of being rejected. A dependency *cycle* was never the hazard -- an object joins the load's list before its own dependencies are walked, so a library naming something already loaded finds it there and stops -- what was unbounded is a chain of distinct names, which the list cannot bound, hence an explicit cap rather than cycle detection. A module's .rofixup section is skipped, and the file header records why. .rofixup is the FDPIC self-relocation list a static executable's crt0 walks to derive its own GOT when no loader is present. A module links -shared -nostartfiles, so no crt0 runs, and the built objects hold exactly one entry there -- the address of the GOT itself, which this loader computes and installs at every entry into module code anyway. Assisted-by: Claude Code:claude-opus-5 Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This commit is contained in:
parent
946b61ca5d
commit
ea78802f8d
14 changed files with 1626 additions and 5 deletions
|
|
@ -47,6 +47,7 @@ ignore-words-list =
|
|||
mot,
|
||||
mis,
|
||||
nexted,
|
||||
nneeded,
|
||||
numer,
|
||||
nwe,
|
||||
oen,
|
||||
|
|
|
|||
|
|
@ -182,9 +182,16 @@
|
|||
|
||||
/* The PIC register is usually R10. It can be R9 is stack checking is enabled
|
||||
* or if the user changes it with -mpic-register on the GCC command line.
|
||||
*
|
||||
* The ARM FDPIC ABI mandates R9, and GCC hardcodes it for -mfdpic, so an
|
||||
* FDPIC build has no choice in the matter.
|
||||
*/
|
||||
|
||||
#define REG_PIC REG_R10
|
||||
#ifdef CONFIG_FDPIC
|
||||
# define REG_PIC REG_R9
|
||||
#else
|
||||
# define REG_PIC REG_R10
|
||||
#endif
|
||||
|
||||
/* CONTROL register */
|
||||
|
||||
|
|
|
|||
|
|
@ -188,9 +188,16 @@
|
|||
|
||||
/* The PIC register is usually R10. It can be R9 is stack checking is enabled
|
||||
* or if the user changes it with -mpic-register on the GCC command line.
|
||||
*
|
||||
* The ARM FDPIC ABI mandates R9, and GCC hardcodes it for -mfdpic, so an
|
||||
* FDPIC build has no choice in the matter.
|
||||
*/
|
||||
|
||||
#define REG_PIC REG_R10
|
||||
#ifdef CONFIG_FDPIC
|
||||
# define REG_PIC REG_R9
|
||||
#else
|
||||
# define REG_PIC REG_R10
|
||||
#endif
|
||||
|
||||
/* CONTROL register */
|
||||
|
||||
|
|
|
|||
|
|
@ -206,6 +206,24 @@
|
|||
#define R_ARM_THM_TLS_DESCSEQ16 129 /* Thumb16 */
|
||||
#define R_ARM_THM_TLS_DESCSEQ32 130 /* Thumb32 */
|
||||
|
||||
/* FDPIC relocations.
|
||||
*
|
||||
* Under the FDPIC ABI each PT_LOAD segment is placed independently, so a
|
||||
* function pointer cannot be a bare code address: it has to carry the data
|
||||
* base its callee will need. A "function descriptor" is that pair, and
|
||||
* these relocations are how the loader is asked to build and reference
|
||||
* them. Values are from the ARM FDPIC ABI as implemented by binutils
|
||||
* (include/elf/arm.h).
|
||||
*/
|
||||
|
||||
#define R_ARM_GOTFUNCDESC 161 /* Data GOT entry holding a descriptor */
|
||||
#define R_ARM_GOTOFFFUNCDESC 162 /* Data GOT-relative descriptor */
|
||||
#define R_ARM_FUNCDESC 163 /* Data Address of a descriptor */
|
||||
#define R_ARM_FUNCDESC_VALUE 164 /* Data The descriptor itself: {code, GOT} */
|
||||
#define R_ARM_TLS_GD32_FDPIC 165 /* Data */
|
||||
#define R_ARM_TLS_LDM32_FDPIC 166 /* Data */
|
||||
#define R_ARM_TLS_IE32_FDPIC 167 /* Data */
|
||||
|
||||
/* Processor specific values for the Phdr p_type field. */
|
||||
|
||||
#define PT_ARM_EXIDX (PT_LOPROC + 1) /* ARM unwind segment. */
|
||||
|
|
|
|||
|
|
@ -612,11 +612,42 @@ CELFFLAGS = $(filter-out --fixed-r10,$(CFLAGS)) -fvisibility=hidden \
|
|||
CXXELFFLAGS = $(filter-out --fixed-r10,$(CXXFLAGS)) -fvisibility=hidden \
|
||||
-mlong-calls
|
||||
|
||||
ifeq ($(CONFIG_FDPIC),y)
|
||||
# An FDPIC module reaches its own data through r9, and that register has
|
||||
# to survive a call into the base firmware. A base firmware routine that
|
||||
# calls back into module code -- qsort() with a module comparison
|
||||
# function is the standard case -- must arrive there with the module's
|
||||
# GOT still in r9, which it will not if the compiler was free to allocate
|
||||
# r9 here.
|
||||
#
|
||||
# This goes in ARCHCPUFLAGS rather than CFLAGS because almost every board
|
||||
# Make.defs assigns
|
||||
#
|
||||
# CFLAGS := $(ARCHCFLAGS) $(ARCHOPTIMIZATION) $(ARCHCPUFLAGS) ...
|
||||
#
|
||||
# with ':=' after including this file, which discards anything added to
|
||||
# CFLAGS here but re-expands ARCHCPUFLAGS. The failure is silent and the
|
||||
# symptom is remote from the cause: the build succeeds and only a callback
|
||||
# from firmware into module code misbehaves, reading its data through a
|
||||
# register the firmware has since reused.
|
||||
#
|
||||
# CPICFLAGS derives from CFLAGS, so this reaches module compiles too.
|
||||
# That is harmless: GCC only rejects --fixed-rN when it names the same
|
||||
# register as -mpic-register, and PIC modules use r10.
|
||||
|
||||
ARCHCPUFLAGS += --fixed-r9
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_PIC),y)
|
||||
ifneq ($(CONFIG_FDPIC),y)
|
||||
# ARCHCFLAGS, not CFLAGS: board Make.defs reassign CFLAGS with ':='
|
||||
# after including this file, which would discard the flag.
|
||||
#
|
||||
# FDPIC reserves r9 above instead; reserving both would cost a register
|
||||
# for nothing.
|
||||
|
||||
ARCHCFLAGS += --fixed-r10
|
||||
endif
|
||||
CELFFLAGS += $(PICFLAGS) -mpic-register=r10
|
||||
CXXELFFLAGS += $(PICFLAGS) -mpic-register=r10
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,10 @@ if(CONFIG_NXFLAT)
|
|||
list(APPEND SRCS nxflat.c)
|
||||
endif()
|
||||
|
||||
if(CONFIG_FDPIC)
|
||||
list(APPEND SRCS fdpic.c)
|
||||
endif()
|
||||
|
||||
# Builtin application interfaces
|
||||
|
||||
if(CONFIG_BUILTIN)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,55 @@ if NXFLAT
|
|||
source "binfmt/libnxflat/Kconfig"
|
||||
endif
|
||||
|
||||
config FDPIC
|
||||
bool "Enable the FDPIC ELF Binary Format"
|
||||
default n
|
||||
select BINFMT_LOADABLE
|
||||
select PIC
|
||||
depends on ARCH_ARM
|
||||
---help---
|
||||
Enable support for FDPIC ELF modules.
|
||||
|
||||
An FDPIC module has independently placeable read-only and writable
|
||||
segments, so its text can be mapped and executed directly out of
|
||||
flash while only the writable segment is copied to RAM -- once per
|
||||
running instance. This requires a filesystem that can expose the
|
||||
media, such as XIPFS or ROMFS, and an arm-uclinuxfdpiceabi
|
||||
toolchain to build the modules.
|
||||
|
||||
The base firmware must reserve the FDPIC register (r9 on ARM) or
|
||||
callbacks from the base firmware into module code will fail.
|
||||
|
||||
if FDPIC
|
||||
|
||||
config FDPIC_LIBPATH
|
||||
string "Directory searched for shared libraries"
|
||||
default "/mnt/xipfs"
|
||||
---help---
|
||||
Where the loader looks for the objects a module names in its
|
||||
DT_NEEDED entries. For execute-in-place this should be a
|
||||
filesystem that can expose its media, so a library's code is
|
||||
mapped rather than copied.
|
||||
|
||||
config FDPIC_STACKSIZE
|
||||
int "FDPIC module stack size"
|
||||
default 2048
|
||||
---help---
|
||||
Size of the stack given to a task created from an FDPIC module.
|
||||
|
||||
config FDPIC_ALLOW_COPY
|
||||
bool "Allow copying text to RAM when execute-in-place is impossible"
|
||||
default n
|
||||
---help---
|
||||
By default a module whose text cannot be mapped directly onto the
|
||||
media fails to load, because copying it to RAM silently forfeits
|
||||
the memory saving that FDPIC was chosen for.
|
||||
|
||||
Enable this during board bring-up to keep modules loading from a
|
||||
filesystem that does not support execute-in-place.
|
||||
|
||||
endif # FDPIC
|
||||
|
||||
config ELF
|
||||
bool "Enable the ELF Binary Format"
|
||||
default n
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ ifeq ($(CONFIG_NXFLAT),y)
|
|||
CSRCS += nxflat.c
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_FDPIC),y)
|
||||
CSRCS += fdpic.c
|
||||
endif
|
||||
|
||||
# Add configured binary modules
|
||||
|
||||
include libnxflat/Make.defs
|
||||
|
|
|
|||
|
|
@ -327,6 +327,38 @@ int nxflat_initialize(void);
|
|||
void nxflat_uninitialize(void);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/****************************************************************************
|
||||
* Name: fdpic_initialize
|
||||
*
|
||||
* Description:
|
||||
* FDPIC support is built unconditionally. However, in order to
|
||||
* use this binary format, this function must be called during system
|
||||
* initialization in order to register the FDPIC binary format.
|
||||
*
|
||||
* Returned Value:
|
||||
* This is a NuttX internal function so it follows the convention that
|
||||
* 0 (OK) is returned on success and a negated errno is returned on
|
||||
* failure.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
int fdpic_initialize(void);
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fdpic_uninitialize
|
||||
*
|
||||
* Description:
|
||||
* Unregister the FDPIC binary loader
|
||||
*
|
||||
* Returned Value:
|
||||
* None
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void fdpic_uninitialize(void);
|
||||
#endif
|
||||
|
||||
#undef EXTERN
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,14 @@ void binfmt_initialize(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
ret = fdpic_initialize();
|
||||
if (ret < 0)
|
||||
{
|
||||
berr("ERROR: fdpic_initialize failed: %d\n", ret);
|
||||
}
|
||||
#endif
|
||||
|
||||
UNUSED(ret);
|
||||
}
|
||||
|
||||
|
|
|
|||
1218
binfmt/fdpic.c
Normal file
1218
binfmt/fdpic.c
Normal file
File diff suppressed because it is too large
Load diff
198
include/nuttx/binfmt/fdpic.h
Normal file
198
include/nuttx/binfmt/fdpic.h
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
/****************************************************************************
|
||||
* include/nuttx/binfmt/fdpic.h
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __INCLUDE_NUTTX_BINFMT_FDPIC_H
|
||||
#define __INCLUDE_NUTTX_BINFMT_FDPIC_H
|
||||
|
||||
/****************************************************************************
|
||||
* Included Files
|
||||
****************************************************************************/
|
||||
|
||||
#include <nuttx/config.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <elf.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <nuttx/fs/fs.h>
|
||||
#include <nuttx/sched.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
/* A function descriptor is the pair that makes FDPIC work: a callee cannot
|
||||
* find its own data from its code address alone, because the two segments
|
||||
* are placed independently, so every function pointer carries the data base
|
||||
* to install alongside the entry point.
|
||||
*/
|
||||
|
||||
#define FDPIC_DESC_WORDS 2
|
||||
|
||||
/* Shared libraries a single object may name in DT_NEEDED */
|
||||
|
||||
#define FDPIC_MAX_NEEDED 8
|
||||
|
||||
/* How deep a chain of libraries depending on libraries may go.
|
||||
*
|
||||
* A cycle cannot run away -- an object joins the load's list before its own
|
||||
* dependencies are walked, so coming back around finds it already there --
|
||||
* but a chain of distinct names has nothing to stop it. The walk recurses
|
||||
* once per link with a path buffer on each frame, so without a bound a
|
||||
* malformed module set overflows the stack of whichever task called the
|
||||
* loader rather than being rejected.
|
||||
*/
|
||||
|
||||
#define FDPIC_MAX_DEPTH 8
|
||||
|
||||
/****************************************************************************
|
||||
* Public Types
|
||||
****************************************************************************/
|
||||
|
||||
struct fdpic_desc_s
|
||||
{
|
||||
uintptr_t entry; /* Address of the code */
|
||||
uintptr_t got; /* Data/GOT base to install in the FDPIC register */
|
||||
};
|
||||
|
||||
/* One loaded object: the module itself, or a shared library it needs.
|
||||
*
|
||||
* A load produces a list of these -- the module first, then whatever its
|
||||
* DT_NEEDED entries pulled in. Text is mapped and therefore shared between
|
||||
* every instance; the writable segment is private to this load, so two
|
||||
* tasks using the same library each get their own copy of its data, which
|
||||
* is the behaviour a library with state has to have.
|
||||
*/
|
||||
|
||||
struct fdpic_loadinfo_s
|
||||
{
|
||||
FAR struct fdpic_loadinfo_s *flink; /* Next object in this load */
|
||||
char name[32]; /* For diagnostics and DT_NEEDED */
|
||||
|
||||
struct file file; /* The module being loaded */
|
||||
Elf32_Ehdr ehdr; /* Copy of the ELF header */
|
||||
|
||||
/* Read-only segment. This is mapped, not copied: on a filesystem that
|
||||
* can expose the media directly this pointer is the flash address and
|
||||
* the text is never in RAM at all.
|
||||
*/
|
||||
|
||||
uintptr_t textaddr; /* Where the RX segment actually is */
|
||||
uintptr_t textvaddr; /* p_vaddr it was linked at */
|
||||
size_t textsize;
|
||||
bool textmapped; /* True if it came from mmap */
|
||||
|
||||
/* Read-write segment. Always a private RAM copy, one per instance. */
|
||||
|
||||
uintptr_t dataaddr; /* Where the RW segment was placed */
|
||||
uintptr_t datavaddr; /* p_vaddr it was linked at */
|
||||
size_t datafilesz; /* Bytes to read from the file */
|
||||
size_t datamemsz; /* Including .bss */
|
||||
size_t dataalloc; /* Total allocation incl. descriptors */
|
||||
|
||||
uintptr_t gotaddr; /* Runtime address of the GOT */
|
||||
uintptr_t entry; /* Runtime entry point */
|
||||
|
||||
/* Reference counted container the scheduler installs into the FDPIC
|
||||
* register on every context switch. Allocated here; freed by
|
||||
* sched_releasetcb() once the last thread using it is gone.
|
||||
*/
|
||||
|
||||
FAR struct dspace_s *dspace;
|
||||
|
||||
/* Pool used to satisfy R_ARM_FUNCDESC, which asks the loader to
|
||||
* manufacture a descriptor and hand back its address.
|
||||
*/
|
||||
|
||||
uintptr_t descpool;
|
||||
uint16_t ndesc; /* Capacity */
|
||||
uint16_t usedesc; /* Next free slot */
|
||||
|
||||
/* Dynamic information, all as link-time virtual addresses */
|
||||
|
||||
/* DT_NEEDED entries, as offsets into DT_STRTAB. Resolved to names once
|
||||
* the read-only segment holding the string table is mapped.
|
||||
*/
|
||||
|
||||
uint32_t needed[FDPIC_MAX_NEEDED];
|
||||
uint8_t nneeded;
|
||||
|
||||
uintptr_t relvaddr;
|
||||
size_t relsize;
|
||||
uintptr_t symtabvaddr;
|
||||
uintptr_t strtabvaddr;
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Inline Functions
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: fdpic_callback
|
||||
*
|
||||
* Description:
|
||||
* Resolve a function pointer that arrived from a caller which may be an
|
||||
* FDPIC module.
|
||||
*
|
||||
* This exists because the two sides disagree about what a function
|
||||
* pointer *is*. Base firmware is not built FDPIC, so to it a function
|
||||
* pointer is a code address and it simply branches to it. An FDPIC
|
||||
* module passes the address of a two-word descriptor instead. A base
|
||||
* firmware routine that takes a callback -- qsort() and bsearch() are the
|
||||
* ones that matter -- therefore branches straight into the module's data
|
||||
* segment and faults.
|
||||
*
|
||||
* The caller is identified by the FDPIC register: a module task runs with
|
||||
* its GOT there, and a plain kernel task runs with zero, because
|
||||
* up_initial_state() only installs a value when the task has a D-Space.
|
||||
*
|
||||
* Only the entry point is taken from the descriptor. The data base is
|
||||
* already correct in the register: the base firmware is built with that
|
||||
* register reserved, so the module's GOT survives the call in.
|
||||
*
|
||||
* Input Parameters:
|
||||
* fn - The pointer as it was received.
|
||||
*
|
||||
* Returned Value:
|
||||
* An address that can be branched to directly.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#if defined(CONFIG_FDPIC) && defined(__thumb__)
|
||||
static inline FAR void *fdpic_callback(FAR void *fn)
|
||||
{
|
||||
uintptr_t base;
|
||||
|
||||
__asm__ __volatile__ ("mov %0, r9" : "=r"(base));
|
||||
|
||||
if (base != 0 && fn != NULL)
|
||||
{
|
||||
return (FAR void *)((FAR struct fdpic_desc_s *)fn)->entry;
|
||||
}
|
||||
|
||||
return fn;
|
||||
}
|
||||
#else
|
||||
# define fdpic_callback(fn) (fn)
|
||||
#endif
|
||||
|
||||
#endif /* __INCLUDE_NUTTX_BINFMT_FDPIC_H */
|
||||
|
|
@ -37,6 +37,10 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
|
||||
/****************************************************************************
|
||||
|
|
@ -114,6 +118,13 @@ FAR void *bsearch(FAR const void *key, FAR const void *base, size_t nel,
|
|||
DEBUGASSERT(base != NULL || nel == 0);
|
||||
DEBUGASSERT(compar != NULL);
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* See qsort(): an FDPIC caller passes a descriptor, not a code address */
|
||||
|
||||
compar = (CODE int (*)(FAR const void *, FAR const void *))
|
||||
fdpic_callback((FAR void *)compar);
|
||||
#endif
|
||||
|
||||
for (lim = nel, lower = (const char *)base; lim != 0; lim >>= 1)
|
||||
{
|
||||
middle = lower + (lim >> 1) * width;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,10 @@
|
|||
#include <sys/param.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef CONFIG_FDPIC
|
||||
# include <nuttx/binfmt/fdpic.h>
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
|
@ -156,8 +160,9 @@ static inline FAR char *med3(FAR char *a, FAR char *b, FAR char *c,
|
|||
*
|
||||
****************************************************************************/
|
||||
|
||||
void qsort(FAR void *base, size_t nel, size_t width,
|
||||
CODE int(*compar)(FAR const void *, FAR const void *))
|
||||
static void qsort_internal(FAR void *base, size_t nel, size_t width,
|
||||
CODE int(*compar)(FAR const void *,
|
||||
FAR const void *))
|
||||
{
|
||||
FAR char *pa;
|
||||
FAR char *pb;
|
||||
|
|
@ -277,7 +282,7 @@ loop:
|
|||
|
||||
if ((r = pb - pa) > width)
|
||||
{
|
||||
qsort(base, r / width, width, compar);
|
||||
qsort_internal(base, r / width, width, compar);
|
||||
}
|
||||
|
||||
if ((r = pd - pc) > width)
|
||||
|
|
@ -289,3 +294,31 @@ loop:
|
|||
goto loop;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: qsort
|
||||
*
|
||||
* Description:
|
||||
* Public entry point. Resolves the comparison function once and then
|
||||
* hands an ordinary pointer to the implementation.
|
||||
*
|
||||
* The split matters: qsort_internal() recurses, and resolving on every
|
||||
* entry would treat an already-resolved code address as a descriptor the
|
||||
* second time round and branch somewhere meaningless.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
void qsort(FAR void *base, size_t nel, size_t width,
|
||||
CODE int(*compar)(FAR const void *, FAR const void *))
|
||||
{
|
||||
#ifdef CONFIG_FDPIC
|
||||
/* An FDPIC module passes the address of a function descriptor, not a
|
||||
* code address.
|
||||
*/
|
||||
|
||||
compar = (CODE int (*)(FAR const void *, FAR const void *))
|
||||
fdpic_callback((FAR void *)compar);
|
||||
#endif
|
||||
|
||||
qsort_internal(base, nel, width, compar);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue