Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

llext: flush logging before unloading extensions #77289

Merged
merged 1 commit into from
Aug 29, 2024

Conversation

lyakh
Copy link
Collaborator

@lyakh lyakh commented Aug 20, 2024

When logging is deferred it can happen that the logger thread attempts to emit a log entry when the memory, containing log data has been unmapped. This can happen e.g. when an LLEXT object was running from dynamically mapped memory, used logging and was unloaded and unmapped before the logger thread had a chance to emit the entry. To avoid this problem, check that the memory, where the format string is residing, hasn't been unmapped. If that does happen, ignore the error and drop the log entry.

This is required to fix thesofproject/sof#9371 . The fix isn't particularly elegant, it's mixing two hardly related APIs: logging and memory mapping. The key problem is that when logging is deferred, memory that it's trying to access might be gone by then. Ideas for a more generic solution are welcome, but - as usually - if properly fixing this requires significant API extensions or changes, maybe we can apply this or something similar as a temporary fix, while working out a generic solution.

Copy link
Collaborator

@teburd teburd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can happen with any extension using logging, not just ones that happen to use memory mapping. @nordic-krch any thoughts?

One option would be for format string in these cases should be copied along with the varargs used. Any pointers in the varargs should likely also be copied over. (E.g. using "%s" with a string parameter in the loaded extensions rodata section)

Alternatively the logging needs to be flushed while unloading an extension perhaps.

So perhaps in llext_unload...

  • Lock everything to prevent preemption on all cores
  • Flush all logging
  • Unmap/release extension memory

@dcpleung
Copy link
Member

I support @teburd's suggestion to flush logging. For logging to NOT copy the format strings and parameters into the buffer, there are specific conditions that need to be met first. Maybe avoid those conditions so you don't have to flush?

@lyakh
Copy link
Collaborator Author

lyakh commented Aug 26, 2024

I support @teburd's suggestion to flush logging. For logging to NOT copy the format strings and parameters into the buffer, there are specific conditions that need to be met first. Maybe avoid those conditions so you don't have to flush?

@dcpleung @teburd yes, I was also thinking about this as a potentially more universal solution. But AFAICS there's currently no API to do that, right? Would this be enough?

void log_flush(void)
{
    unsigned int n = atomic_get(&buffered_cnt);
    for (unsigned int i = 0; i < n; i++) {
        log_process();
    }
}

@lyakh
Copy link
Collaborator Author

lyakh commented Aug 26, 2024

void log_flush(void)
{
    unsigned int n = atomic_get(&buffered_cnt);
    for (unsigned int i = 0; i < n; i++) {
        log_process();
    }
}

hmm, just tested, this doesn't quite work unfortunately. The while (log_process()) ; version doesn't work either

@teburd
Copy link
Collaborator

teburd commented Aug 26, 2024

void log_flush(void)
{
    unsigned int n = atomic_get(&buffered_cnt);
    for (unsigned int i = 0; i < n; i++) {
        log_process();
    }
}

hmm, just tested, this doesn't quite work unfortunately

This seems to be used in a few places...

while (log_process()) {}

You'd also need to perhaps disable interrupts while flushing though to avoid issues

key = irq_lock();
while (log_process()) {}
irq_unlock(key);

@lyakh
Copy link
Collaborator Author

lyakh commented Aug 26, 2024

key = irq_lock();
while (log_process()) {}
irq_unlock(key);

no, didn't help either, unfortunately

@lyakh
Copy link
Collaborator Author

lyakh commented Aug 26, 2024

This seems to be used in a few places...

@teburd btw, where do you see such examples? I only see this when panicking and in a user-space logging test

@teburd
Copy link
Collaborator

teburd commented Aug 26, 2024

Ok maybe a new, possibly pointless idea...

Temporarily boost the priority of the logging thread and tell it to run, locking all the interrupts and junk while unloading the extension.

#include <zephyr/logging/log_ctrl.h>

extern k_thread logging_thread;

void llext_log_flush(void)
{
   int cur_prio;

   /* boost logging thread prio */
   cur_prio = k_thread_priority_get(&logging_thread);
   k_thread_priority_set(&logging_thread, K_HIGHEST_THREAD_PRIO);
   log_thread_trigger();
   k_thread_priority_set(&logging_thread, cur_prio);

}

void llext_unload(...)
{
  int key, cur_prio;

  key = irq_lock();

  /* boost current thread priority */
  cur_prio = k_thread_get_priority(&k_current_get());
  k_thread_set_priority(&k_current_get(), K_HIGHEST_THREAD_PRIO+1);

  llext_log_flush();

  /* unload work done here */

  k_thread_set_priority(&k_current_get(), cur_prio);
  irq_unlock(key);
}

@zephyrbot zephyrbot added the area: llext Linkable Loadable Extensions label Aug 27, 2024
@zephyrbot zephyrbot requested a review from pillo79 August 27, 2024 16:10
@teburd teburd changed the title log: don't dereference unmapped memory in z_cbvprintf_impl() llext: flush logging before unloading extensions Aug 27, 2024
@teburd teburd assigned teburd and unassigned nordic-krch Aug 27, 2024
@teburd
Copy link
Collaborator

teburd commented Aug 27, 2024

Re-assigned as this now no longer modifies logging code, but I'd still appreciate feedback from @nordic-krch on the approach

@lyakh lyakh force-pushed the log branch 3 times, most recently from 6efcab3 to ad630d4 Compare August 28, 2024 08:25
pillo79
pillo79 previously approved these changes Aug 28, 2024
Copy link
Collaborator

@pillo79 pillo79 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!
I would also be in favor of moving it to a log_flush function so that it would be universally available, if the approach is acceptable to the logging subsystem.

pillo79
pillo79 previously approved these changes Aug 28, 2024
@lyakh
Copy link
Collaborator Author

lyakh commented Aug 28, 2024


static void llext_log_flush(void)
{
#if defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_MINIMAL)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should only be done when CONFIG_LOG_MODE_DEFERRED is enabled. Both minimal and immediate modes do not have log processing thread.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dcpleung you're right!

@lyakh lyakh force-pushed the log branch 2 times, most recently from dec0986 to a44d375 Compare August 29, 2024 11:55
Extensions could have used logging, when log processing is
deferred, the logging thread can run after the extension has
been unloaded and thereby access invalid memory addresses.
Make sure to flush all logs before unloading extensions.

Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
@carlescufi carlescufi merged commit 9dffac0 into zephyrproject-rtos:main Aug 29, 2024
23 checks passed
@lyakh lyakh deleted the log branch August 30, 2024 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] LLEXT: delayed logging can access memory after it's unmapped
7 participants