Skip to content

[libc][libdl] Fix dlopen() failing to find already-loaded modules#11269

Draft
Copilot wants to merge 4 commits intomasterfrom
copilot/fix-dlopen-name-mismatch
Draft

[libc][libdl] Fix dlopen() failing to find already-loaded modules#11269
Copilot wants to merge 4 commits intomasterfrom
copilot/fix-dlopen-name-mismatch

Conversation

Copy link
Contributor

Copilot AI commented Mar 19, 2026

dlopen() passes the full path to dlmodule_find(), but _dlmodule_set_name() stores only the stripped basename. The lookup always fails, causing modules to reload on every call with nref++ never reached.

void *h1 = dlopen("/mnt/sdcard/apps/clock.so", 0);  // loads, name stored as "clock"
void *h2 = dlopen("/mnt/sdcard/apps/clock.so", 0);  // loads again (find fails), h1 != h2

Changes

  • New dlmodule_extract_name() function — Extracts module name from path, stripping directory and extension. Shared between dlopen() and _dlmodule_set_name() to ensure consistent naming.

  • Fix extension detection — Original code found last . in entire path, not filename. /mnt/v1.2/app.so incorrectly extracted v1 instead of app.

  • Edge case handling — Hidden files like .hidden use entire filename; .hidden.so strips to .hidden.

Before/After

Path Before After
/mnt/sdcard/apps/clock.so lookup: full path, stored: clock both: clock
/mnt/v1.2/app.so v1 (bug) app
Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] dlopen() fails to find already-loaded module — name mismatch between dlmodule_find() and _dlmodule_set_name()</issue_title>
<issue_description>### 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:

  1. Module reloaded on every dlopen() call — the nref++ branch is never reached
  2. Memory leak — duplicate module instances accumulate in RAM
  3. 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';
    }
 
   ...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes RT-Thread/rt-thread#11268

<!-- START COPILOT CODING AGENT TIPS -->
---Let Copilot coding agent [set things up for you](https://github.com/RT-Thread/rt-thread/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI and others added 3 commits March 19, 2026 02:34
Fix name mismatch between dlmodule_find() and _dlmodule_set_name() that
caused dlopen() to always reload modules instead of reusing them:

1. In dlopen(): Extract module name from full path before calling
   dlmodule_find(), matching the stripped name stored by _dlmodule_set_name().
   This allows dlopen() to properly find and reuse already-loaded modules.

2. In _dlmodule_set_name(): Fix bug where extension detection searched
   the entire path instead of just the filename portion. For paths like
   "/mnt/v1.2/app.so", it would incorrectly find "." in "v1.2" directory
   name instead of the ".so" extension.

Fixes: Module reloading on every dlopen() call, memory leaks from
duplicate module instances, and dlclose() cleanup issues.

Co-authored-by: BernardXiong <1241087+BernardXiong@users.noreply.github.com>
… cases

Address code review feedback:
1. Extract path stripping logic into shared dlmodule_extract_name() function
   to avoid code duplication between dlopen() and _dlmodule_set_name().
2. Fix edge case handling for filenames starting with dot (e.g., .hidden).
3. Add proper documentation for the new function.

This ensures consistent module name extraction across all code paths.

Co-authored-by: BernardXiong <1241087+BernardXiong@users.noreply.github.com>
…_name

Clarify the behavior of edge cases in documentation:
- Added more examples to the function documentation
- Fixed misleading comment about dot handling
- Clarified the defensive check purpose

Co-authored-by: BernardXiong <1241087+BernardXiong@users.noreply.github.com>
Copilot AI changed the title [WIP] [Bug] Fix dlopen() fails to find already-loaded module [libc][libdl] Fix dlopen() failing to find already-loaded modules Mar 19, 2026
Copilot AI requested a review from BernardXiong March 19, 2026 02:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants