RT-Thread Version
master (latest)
Hardware Type/Architectures
N/A — platform-independent logic bug in components/libc/posix/libdl/
Develop Toolchain
Microsoft VScode
Describe the bug
Bug Description
Summary
When dlopen() is called with a full file path (e.g. "/mnt/sdcard/apps/clock.so"), it passes the full path to dlmodule_find() for lookup. However, during module loading, _dlmodule_set_name() strips the path and extension, storing only the bare filename (e.g. "clock") into module->parent.name. Since dlmodule_find() uses rt_object_find() which matches against object->name, the lookup always fails for previously loaded modules.
This causes:
- Module reloaded on every
dlopen() call — the nref++ branch is never reached
- Memory leak — duplicate module instances accumulate in RAM
dlclose() cannot properly clean up — each dlopen() returns a different handle pointing to a different copy
Root Cause Analysis
Step 1: dlopen() passes full path to dlmodule_find()
File: dlopen.c, line 38-44
void *dlopen(const char *filename, int flags)
{
// ...
fullpath = (char *)filename; /* e.g. "/mnt/sdcard/apps/clock.so" */
rt_enter_critical();
module = dlmodule_find(fullpath); // <-- passes full path as-is
if (module != RT_NULL)
{
rt_exit_critical();
module->nref++; // <-- NEVER reached due to this bug
}
else
{
rt_exit_critical();
module = dlmodule_load(fullpath); // <-- always falls through here
}
// ...
}
Step 2: dlmodule_find() does direct name matching with no path processing
File: dlmodule.c, line 1130-1142
struct rt_dlmodule *dlmodule_find(const char *name)
{
rt_object_t object;
struct rt_dlmodule *ret = RT_NULL;
object = rt_object_find(name, RT_Object_Class_Module);
// ↑ tries to match name == "/mnt/sdcard/apps/clock.so"
// but no module is registered under that name
if (object) {
ret = (struct rt_dlmodule *) object;
}
return ret;
}
Step 3: _dlmodule_set_name() strips path and extension before storing
File: dlmodule.c, line 74-99
static void _dlmodule_set_name(struct rt_dlmodule *module, const char *path)
{
// ...
while (*ptr != '\0')
{
if (*ptr == '/')
first = ptr + 1; // skip to after last '/'
if (*ptr == '.')
end = ptr - 1; // stop before last '.'
ptr ++;
}
// For path "/mnt/sdcard/apps/clock.so":
// → stores "clock" into object->name
}
The Mismatch
| Operation |
Key used |
Value |
dlmodule_find() query |
full path |
"/mnt/sdcard/apps/clock.so" |
object->name stored by _dlmodule_set_name() |
stripped name |
"clock" |
These will never match.
Steps to Reproduce
#include <dlfcn.h>
/* First dlopen — module loaded, name stored as "clock" */
void *h1 = dlopen("/mnt/sdcard/apps/clock.so", 0);
/* Second dlopen — should find existing module and nref++,
but actually loads a second copy because find fails */
void *h2 = dlopen("/mnt/sdcard/apps/clock.so", 0);
/* h1 != h2 — two independent copies of clock.so now in memory */
Expected Behavior
The second dlopen() call should find the already-loaded module, increment nref, and return the same module handle.
Actual Behavior
Every dlopen() call with a full path loads a new copy of the module. The nref++ reuse branch in dlopen() is effectively dead code — nref is only ever incremented to 1 inside dlmodule_load() itself (line 753), but never reaches 2+ through the intended dlopen() reuse path.
Suggested Fix
Apply the same path-stripping logic before calling dlmodule_find() in dlopen(). Minimal patch:
/* dlopen.c — extract module name from path before find */
void *dlopen(const char *filename, int flags)
{
struct rt_dlmodule *module;
char module_name[RT_NAME_MAX];
RT_ASSERT(filename != RT_NULL);
/* Strip path and extension — same logic as _dlmodule_set_name() */
{
const char *first, *end, *ptr;
int size;
ptr = first = filename;
end = filename + rt_strlen(filename);
while (*ptr != '\0')
{
if (*ptr == '/')
first = ptr + 1;
if (*ptr == '.')
end = ptr - 1;
ptr++;
}
size = end - first + 1;
if (size > RT_NAME_MAX) size = RT_NAME_MAX;
rt_memset(module_name, 0x00, sizeof(module_name));
rt_strncpy(module_name, first, size);
module_name[RT_NAME_MAX - 1] = '\0';
}
rt_enter_critical();
module = dlmodule_find(module_name); /* now matches object->name */
if (module != RT_NULL)
{
rt_exit_critical();
module->nref++;
}
else
{
rt_exit_critical();
module = dlmodule_load(filename); /* still pass full path for file I/O */
}
return (void *)module;
}
Alternatively, the stripping logic could be moved into dlmodule_find() itself, but that may affect other callers (e.g. dlrun() which already passes a bare module name).
Additional Notes
-
Dead code on dlopen.c line 28: The condition if (0) //filename[0] != '/' permanently disables the relative-path handling branch. This appears to be a debugging leftover.
-
Secondary bug in _dlmodule_set_name(): The function finds the last . in the entire path string, not just in the filename portion. For paths like /mnt/v1.2/app.so, end would point into the directory component (v1 instead of app), producing an incorrect module name.
Other additional context
No response
RT-Thread Version
master (latest)
Hardware Type/Architectures
N/A — platform-independent logic bug in components/libc/posix/libdl/
Develop Toolchain
Microsoft VScode
Describe the bug
Bug Description
Summary
When
dlopen()is called with a full file path (e.g."/mnt/sdcard/apps/clock.so"), it passes the full path todlmodule_find()for lookup. However, during module loading,_dlmodule_set_name()strips the path and extension, storing only the bare filename (e.g."clock") intomodule->parent.name. Sincedlmodule_find()usesrt_object_find()which matches againstobject->name, the lookup always fails for previously loaded modules.This causes:
dlopen()call — thenref++branch is never reacheddlclose()cannot properly clean up — eachdlopen()returns a different handle pointing to a different copyRoot Cause Analysis
Step 1:
dlopen()passes full path todlmodule_find()File:
dlopen.c, line 38-44Step 2:
dlmodule_find()does direct name matching with no path processingFile:
dlmodule.c, line 1130-1142Step 3:
_dlmodule_set_name()strips path and extension before storingFile:
dlmodule.c, line 74-99The Mismatch
dlmodule_find()query"/mnt/sdcard/apps/clock.so"object->namestored by_dlmodule_set_name()"clock"These will never match.
Steps to Reproduce
Expected Behavior
The second
dlopen()call should find the already-loaded module, incrementnref, and return the same module handle.Actual Behavior
Every
dlopen()call with a full path loads a new copy of the module. Thenref++reuse branch indlopen()is effectively dead code —nrefis only ever incremented to 1 insidedlmodule_load()itself (line 753), but never reaches 2+ through the intendeddlopen()reuse path.Suggested Fix
Apply the same path-stripping logic before calling
dlmodule_find()indlopen(). Minimal patch:Alternatively, the stripping logic could be moved into
dlmodule_find()itself, but that may affect other callers (e.g.dlrun()which already passes a bare module name).Additional Notes
Dead code on
dlopen.cline 28: The conditionif (0) //filename[0] != '/'permanently disables the relative-path handling branch. This appears to be a debugging leftover.Secondary bug in
_dlmodule_set_name(): The function finds the last.in the entire path string, not just in the filename portion. For paths like/mnt/v1.2/app.so,endwould point into the directory component (v1instead ofapp), producing an incorrect module name.Other additional context
No response