diff mbox series

[RFC] tcg/plugins: implement a qemu_plugin_user_exit helper

Message ID 20210719123732.24457-1-alex.bennee@linaro.org
State Superseded
Headers show
Series [RFC] tcg/plugins: implement a qemu_plugin_user_exit helper | expand

Commit Message

Alex Bennée July 19, 2021, 12:37 p.m. UTC
In user-mode emulation there is a small race between preexit_cleanup
and exit_group() which means we may end up calling instrumented
instructions before the kernel reaps child threads. To solve this we
implement a new helper which ensures the callbacks are flushed along
with any translations before we let the host do it's a thing.

While we are at it make the documentation of
qemu_plugin_register_atexit_cb clearer as to what the user can expect.

Signed-off-by: Alex Bennée <alex.bennee@linaro.org>

---
 include/qemu/plugin.h      | 12 ++++++++++++
 include/qemu/qemu-plugin.h | 13 +++++++++++++
 bsd-user/syscall.c         |  6 +++---
 linux-user/exit.c          |  2 +-
 plugins/core.c             | 33 +++++++++++++++++++++++++++++++++
 5 files changed, 62 insertions(+), 4 deletions(-)

-- 
2.32.0.264.g75ae10bc75

Comments

Alex Bennée July 19, 2021, 1:56 p.m. UTC | #1
Alex Bennée <alex.bennee@linaro.org> writes:

> In user-mode emulation there is a small race between preexit_cleanup

> and exit_group() which means we may end up calling instrumented

> instructions before the kernel reaps child threads. To solve this we

> implement a new helper which ensures the callbacks are flushed along

> with any translations before we let the host do it's a thing.

>

> While we are at it make the documentation of

> qemu_plugin_register_atexit_cb clearer as to what the user can expect.

>

<snip>
>  

> +/*

> + * Handle exit from linux-user. Unlike the normal atexit() mechanism

> + * we need to handle the clean-up manually as it's possible threads

> + * are still running. We need to remove all callbacks from code

> + * generation, flush the current translations and then we can safely

> + * trigger the exit callbacks.

> + */

> +

> +void qemu_plugin_user_exit(void)

> +{

> +    enum qemu_plugin_event ev;

> +

> +    QEMU_LOCK_GUARD(&plugin.lock);

> +

> +    start_exclusive();

> +

> +    /* un-register all callbacks except the final AT_EXIT one */

> +    for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) {

> +        if (ev != QEMU_PLUGIN_EV_ATEXIT) {

> +            struct qemu_plugin_ctx *ctx;

> +            QTAILQ_FOREACH(ctx, &plugin.ctxs, entry) {

> +                plugin_unregister_cb__locked(ctx, ev);

> +            }

> +        }

> +    }

> +

> +    tb_flush(current_cpu);


We also need to disable memory helpers during the exclusive period as
that is another route into a callback:

--8<---------------cut here---------------start------------->8---
modified   plugins/core.c
@@ -498,6 +499,7 @@ void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,
 void qemu_plugin_user_exit(void)
 {
     enum qemu_plugin_event ev;
+    CPUState *cpu;
 
     QEMU_LOCK_GUARD(&plugin.lock);
 
@@ -514,6 +516,11 @@ void qemu_plugin_user_exit(void)
     }
 
     tb_flush(current_cpu);
+
+    CPU_FOREACH(cpu) {
+        qemu_plugin_disable_mem_helpers(cpu);
+    }
+
     end_exclusive();
 
     /* now it's safe to handle the exit case */
--8<---------------cut here---------------end--------------->8---



> +    end_exclusive();

> +

> +    /* now it's safe to handle the exit case */

> +    qemu_plugin_atexit_cb();

> +}

> +

>  /*

>   * Call this function after longjmp'ing to the main loop. It's possible that the

>   * last instruction of a TB might have used helpers, and therefore the



-- 
Alex Bennée
Warner Losh July 19, 2021, 6:03 p.m. UTC | #2
On Mon, Jul 19, 2021, 7:57 AM Alex Bennée <alex.bennee@linaro.org> wrote:

>

> Alex Bennée <alex.bennee@linaro.org> writes:

>

> > In user-mode emulation there is a small race between preexit_cleanup

> > and exit_group() which means we may end up calling instrumented

> > instructions before the kernel reaps child threads. To solve this we

> > implement a new helper which ensures the callbacks are flushed along

> > with any translations before we let the host do it's a thing.

> >

> > While we are at it make the documentation of

> > qemu_plugin_register_atexit_cb clearer as to what the user can expect.

> >

> <snip>

> >

> > +/*

> > + * Handle exit from linux-user. Unlike the normal atexit() mechanism

> > + * we need to handle the clean-up manually as it's possible threads

> > + * are still running. We need to remove all callbacks from code

> > + * generation, flush the current translations and then we can safely

> > + * trigger the exit callbacks.

> > + */

> > +

> > +void qemu_plugin_user_exit(void)

> > +{

> > +    enum qemu_plugin_event ev;

> > +

> > +    QEMU_LOCK_GUARD(&plugin.lock);

> > +

> > +    start_exclusive();

> > +

> > +    /* un-register all callbacks except the final AT_EXIT one */

> > +    for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) {

> > +        if (ev != QEMU_PLUGIN_EV_ATEXIT) {

> > +            struct qemu_plugin_ctx *ctx;

> > +            QTAILQ_FOREACH(ctx, &plugin.ctxs, entry) {

> > +                plugin_unregister_cb__locked(ctx, ev);

> > +            }

> > +        }

> > +    }

> > +

> > +    tb_flush(current_cpu);

>

> We also need to disable memory helpers during the exclusive period as

> that is another route into a callback:

> --8<---------------cut here---------------start------------->8---

> modified   plugins/core.c

> @@ -498,6 +499,7 @@ void qemu_plugin_register_atexit_cb(qemu_plugin_id_t

> id,

>  void qemu_plugin_user_exit(void)

>  {

>      enum qemu_plugin_event ev;

> +    CPUState *cpu;

>

>      QEMU_LOCK_GUARD(&plugin.lock);

>

> @@ -514,6 +516,11 @@ void qemu_plugin_user_exit(void)

>      }

>

>      tb_flush(current_cpu);

> +

> +    CPU_FOREACH(cpu) {

> +        qemu_plugin_disable_mem_helpers(cpu);

> +    }

> +

>      end_exclusive();

>

>      /* now it's safe to handle the exit case */

> --8<---------------cut here---------------end--------------->8---

>


I think both of these are find from a bsd-user point of view.

Warner


> > +    end_exclusive();

> > +

> > +    /* now it's safe to handle the exit case */

> > +    qemu_plugin_atexit_cb();

> > +}

> > +

> >  /*

> >   * Call this function after longjmp'ing to the main loop. It's possible

> that the

> >   * last instruction of a TB might have used helpers, and therefore the

>

>

> --

> Alex Bennée

>
<div dir="auto"><div><br><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Jul 19, 2021, 7:57 AM Alex Bennée &lt;<a href="mailto:alex.bennee@linaro.org">alex.bennee@linaro.org</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
Alex Bennée &lt;<a href="mailto:alex.bennee@linaro.org" target="_blank" rel="noreferrer">alex.bennee@linaro.org</a>&gt; writes:<br>
<br>
&gt; In user-mode emulation there is a small race between preexit_cleanup<br>
&gt; and exit_group() which means we may end up calling instrumented<br>
&gt; instructions before the kernel reaps child threads. To solve this we<br>
&gt; implement a new helper which ensures the callbacks are flushed along<br>
&gt; with any translations before we let the host do it&#39;s a thing.<br>
&gt;<br>
&gt; While we are at it make the documentation of<br>
&gt; qemu_plugin_register_atexit_cb clearer as to what the user can expect.<br>
&gt;<br>
&lt;snip&gt;<br>
&gt;  <br>
&gt; +/*<br>
&gt; + * Handle exit from linux-user. Unlike the normal atexit() mechanism<br>
&gt; + * we need to handle the clean-up manually as it&#39;s possible threads<br>
&gt; + * are still running. We need to remove all callbacks from code<br>
&gt; + * generation, flush the current translations and then we can safely<br>
&gt; + * trigger the exit callbacks.<br>
&gt; + */<br>
&gt; +<br>
&gt; +void qemu_plugin_user_exit(void)<br>
&gt; +{<br>
&gt; +    enum qemu_plugin_event ev;<br>
&gt; +<br>
&gt; +    QEMU_LOCK_GUARD(&amp;plugin.lock);<br>
&gt; +<br>
&gt; +    start_exclusive();<br>
&gt; +<br>
&gt; +    /* un-register all callbacks except the final AT_EXIT one */<br>
&gt; +    for (ev = 0; ev &lt; QEMU_PLUGIN_EV_MAX; ev++) {<br>
&gt; +        if (ev != QEMU_PLUGIN_EV_ATEXIT) {<br>
&gt; +            struct qemu_plugin_ctx *ctx;<br>
&gt; +            QTAILQ_FOREACH(ctx, &amp;plugin.ctxs, entry) {<br>
&gt; +                plugin_unregister_cb__locked(ctx, ev);<br>
&gt; +            }<br>
&gt; +        }<br>
&gt; +    }<br>
&gt; +<br>
&gt; +    tb_flush(current_cpu);<br>
<br>
We also need to disable memory helpers during the exclusive period as<br>
that is another route into a callback:<br>
--8&lt;---------------cut here---------------start-------------&gt;8---<br>
modified   plugins/core.c<br>
@@ -498,6 +499,7 @@ void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,<br>
 void qemu_plugin_user_exit(void)<br>
 {<br>
     enum qemu_plugin_event ev;<br>
+    CPUState *cpu;<br>
<br>
     QEMU_LOCK_GUARD(&amp;plugin.lock);<br>
<br>
@@ -514,6 +516,11 @@ void qemu_plugin_user_exit(void)<br>
     }<br>
<br>
     tb_flush(current_cpu);<br>
+<br>
+    CPU_FOREACH(cpu) {<br>
+        qemu_plugin_disable_mem_helpers(cpu);<br>
+    }<br>
+<br>
     end_exclusive();<br>
<br>
     /* now it&#39;s safe to handle the exit case */<br>
--8&lt;---------------cut here---------------end---------------&gt;8---<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">I think both of these are find from a bsd-user point of view.</div><div dir="auto"><br></div><div dir="auto">Warner </div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
&gt; +    end_exclusive();<br>
&gt; +<br>
&gt; +    /* now it&#39;s safe to handle the exit case */<br>
&gt; +    qemu_plugin_atexit_cb();<br>
&gt; +}<br>
&gt; +<br>
&gt;  /*<br>
&gt;   * Call this function after longjmp&#39;ing to the main loop. It&#39;s possible that the<br>
&gt;   * last instruction of a TB might have used helpers, and therefore the<br>
<br>
<br>
-- <br>
Alex Bennée<br>
</blockquote></div></div></div>
Alex Bennée July 19, 2021, 7:21 p.m. UTC | #3
Warner Losh <imp@bsdimp.com> writes:

> On Mon, Jul 19, 2021, 7:57 AM Alex Bennée <alex.bennee@linaro.org> wrote:

>

>  Alex Bennée <alex.bennee@linaro.org> writes:

>

>  > In user-mode emulation there is a small race between preexit_cleanup

>  > and exit_group() which means we may end up calling instrumented

>  > instructions before the kernel reaps child threads. To solve this we

>  > implement a new helper which ensures the callbacks are flushed along

>  > with any translations before we let the host do it's a thing.

>  >

>  > While we are at it make the documentation of

>  > qemu_plugin_register_atexit_cb clearer as to what the user can expect.

>  >

>  <snip>

>  >  

>  > +/*

>  > + * Handle exit from linux-user. Unlike the normal atexit() mechanism

>  > + * we need to handle the clean-up manually as it's possible threads

>  > + * are still running. We need to remove all callbacks from code

>  > + * generation, flush the current translations and then we can safely

>  > + * trigger the exit callbacks.

>  > + */

>  > +

>  > +void qemu_plugin_user_exit(void)

>  > +{

>  > +    enum qemu_plugin_event ev;

>  > +

>  > +    QEMU_LOCK_GUARD(&plugin.lock);

>  > +

>  > +    start_exclusive();

>  > +

>  > +    /* un-register all callbacks except the final AT_EXIT one */

>  > +    for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) {

>  > +        if (ev != QEMU_PLUGIN_EV_ATEXIT) {

>  > +            struct qemu_plugin_ctx *ctx;

>  > +            QTAILQ_FOREACH(ctx, &plugin.ctxs, entry) {

>  > +                plugin_unregister_cb__locked(ctx, ev);

>  > +            }

>  > +        }

>  > +    }

>  > +

>  > +    tb_flush(current_cpu);

>

>  We also need to disable memory helpers during the exclusive period as

>  that is another route into a callback:

>  --8<---------------cut here---------------start------------->8---

>  modified   plugins/core.c

>  @@ -498,6 +499,7 @@ void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,

>   void qemu_plugin_user_exit(void)

>   {

>       enum qemu_plugin_event ev;

>  +    CPUState *cpu;

>

>       QEMU_LOCK_GUARD(&plugin.lock);

>

>  @@ -514,6 +516,11 @@ void qemu_plugin_user_exit(void)

>       }

>

>       tb_flush(current_cpu);

>  +

>  +    CPU_FOREACH(cpu) {

>  +        qemu_plugin_disable_mem_helpers(cpu);

>  +    }

>  +

>       end_exclusive();

>

>       /* now it's safe to handle the exit case */

>  --8<---------------cut here---------------end--------------->8---

>

> I think both of these are find from a bsd-user point of view.


Acked-by: or Reviewed-by:?


-- 
Alex Bennée
Warner Losh July 19, 2021, 7:26 p.m. UTC | #4
On Mon, Jul 19, 2021, 1:22 PM Alex Bennée <alex.bennee@linaro.org> wrote:

>

> Warner Losh <imp@bsdimp.com> writes:

>

> > On Mon, Jul 19, 2021, 7:57 AM Alex Bennée <alex.bennee@linaro.org>

> wrote:

> >

> >  Alex Bennée <alex.bennee@linaro.org> writes:

> >

> >  > In user-mode emulation there is a small race between preexit_cleanup

> >  > and exit_group() which means we may end up calling instrumented

> >  > instructions before the kernel reaps child threads. To solve this we

> >  > implement a new helper which ensures the callbacks are flushed along

> >  > with any translations before we let the host do it's a thing.

> >  >

> >  > While we are at it make the documentation of

> >  > qemu_plugin_register_atexit_cb clearer as to what the user can expect.

> >  >

> >  <snip>

> >  >

> >  > +/*

> >  > + * Handle exit from linux-user. Unlike the normal atexit() mechanism

> >  > + * we need to handle the clean-up manually as it's possible threads

> >  > + * are still running. We need to remove all callbacks from code

> >  > + * generation, flush the current translations and then we can safely

> >  > + * trigger the exit callbacks.

> >  > + */

> >  > +

> >  > +void qemu_plugin_user_exit(void)

> >  > +{

> >  > +    enum qemu_plugin_event ev;

> >  > +

> >  > +    QEMU_LOCK_GUARD(&plugin.lock);

> >  > +

> >  > +    start_exclusive();

> >  > +

> >  > +    /* un-register all callbacks except the final AT_EXIT one */

> >  > +    for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) {

> >  > +        if (ev != QEMU_PLUGIN_EV_ATEXIT) {

> >  > +            struct qemu_plugin_ctx *ctx;

> >  > +            QTAILQ_FOREACH(ctx, &plugin.ctxs, entry) {

> >  > +                plugin_unregister_cb__locked(ctx, ev);

> >  > +            }

> >  > +        }

> >  > +    }

> >  > +

> >  > +    tb_flush(current_cpu);

> >

> >  We also need to disable memory helpers during the exclusive period as

> >  that is another route into a callback:

> >  --8<---------------cut here---------------start------------->8---

> >  modified   plugins/core.c

> >  @@ -498,6 +499,7 @@ void

> qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,

> >   void qemu_plugin_user_exit(void)

> >   {

> >       enum qemu_plugin_event ev;

> >  +    CPUState *cpu;

> >

> >       QEMU_LOCK_GUARD(&plugin.lock);

> >

> >  @@ -514,6 +516,11 @@ void qemu_plugin_user_exit(void)

> >       }

> >

> >       tb_flush(current_cpu);

> >  +

> >  +    CPU_FOREACH(cpu) {

> >  +        qemu_plugin_disable_mem_helpers(cpu);

> >  +    }

> >  +

> >       end_exclusive();

> >

> >       /* now it's safe to handle the exit case */

> >  --8<---------------cut here---------------end--------------->8---

> >

> > I think both of these are find from a bsd-user point of view.

>

> Acked-by: or Reviewed-by:?

>


Sorry I wasn't clear.

Acked-by: Warner Losh <imp@bsdimp.com>




> --

> Alex Bennée

>
<div dir="auto"><div><br><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Jul 19, 2021, 1:22 PM Alex Bennée &lt;<a href="mailto:alex.bennee@linaro.org">alex.bennee@linaro.org</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
Warner Losh &lt;<a href="mailto:imp@bsdimp.com" target="_blank" rel="noreferrer">imp@bsdimp.com</a>&gt; writes:<br>
<br>
&gt; On Mon, Jul 19, 2021, 7:57 AM Alex Bennée &lt;<a href="mailto:alex.bennee@linaro.org" target="_blank" rel="noreferrer">alex.bennee@linaro.org</a>&gt; wrote:<br>
&gt;<br>
&gt;  Alex Bennée &lt;<a href="mailto:alex.bennee@linaro.org" target="_blank" rel="noreferrer">alex.bennee@linaro.org</a>&gt; writes:<br>
&gt;<br>
&gt;  &gt; In user-mode emulation there is a small race between preexit_cleanup<br>
&gt;  &gt; and exit_group() which means we may end up calling instrumented<br>
&gt;  &gt; instructions before the kernel reaps child threads. To solve this we<br>
&gt;  &gt; implement a new helper which ensures the callbacks are flushed along<br>
&gt;  &gt; with any translations before we let the host do it&#39;s a thing.<br>
&gt;  &gt;<br>
&gt;  &gt; While we are at it make the documentation of<br>
&gt;  &gt; qemu_plugin_register_atexit_cb clearer as to what the user can expect.<br>
&gt;  &gt;<br>
&gt;  &lt;snip&gt;<br>
&gt;  &gt;  <br>
&gt;  &gt; +/*<br>
&gt;  &gt; + * Handle exit from linux-user. Unlike the normal atexit() mechanism<br>
&gt;  &gt; + * we need to handle the clean-up manually as it&#39;s possible threads<br>
&gt;  &gt; + * are still running. We need to remove all callbacks from code<br>
&gt;  &gt; + * generation, flush the current translations and then we can safely<br>
&gt;  &gt; + * trigger the exit callbacks.<br>
&gt;  &gt; + */<br>
&gt;  &gt; +<br>
&gt;  &gt; +void qemu_plugin_user_exit(void)<br>
&gt;  &gt; +{<br>
&gt;  &gt; +    enum qemu_plugin_event ev;<br>
&gt;  &gt; +<br>
&gt;  &gt; +    QEMU_LOCK_GUARD(&amp;plugin.lock);<br>
&gt;  &gt; +<br>
&gt;  &gt; +    start_exclusive();<br>
&gt;  &gt; +<br>
&gt;  &gt; +    /* un-register all callbacks except the final AT_EXIT one */<br>
&gt;  &gt; +    for (ev = 0; ev &lt; QEMU_PLUGIN_EV_MAX; ev++) {<br>
&gt;  &gt; +        if (ev != QEMU_PLUGIN_EV_ATEXIT) {<br>
&gt;  &gt; +            struct qemu_plugin_ctx *ctx;<br>
&gt;  &gt; +            QTAILQ_FOREACH(ctx, &amp;plugin.ctxs, entry) {<br>
&gt;  &gt; +                plugin_unregister_cb__locked(ctx, ev);<br>
&gt;  &gt; +            }<br>
&gt;  &gt; +        }<br>
&gt;  &gt; +    }<br>
&gt;  &gt; +<br>
&gt;  &gt; +    tb_flush(current_cpu);<br>
&gt;<br>
&gt;  We also need to disable memory helpers during the exclusive period as<br>
&gt;  that is another route into a callback:<br>
&gt;  --8&lt;---------------cut here---------------start-------------&gt;8---<br>
&gt;  modified   plugins/core.c<br>
&gt;  @@ -498,6 +499,7 @@ void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,<br>
&gt;   void qemu_plugin_user_exit(void)<br>
&gt;   {<br>
&gt;       enum qemu_plugin_event ev;<br>
&gt;  +    CPUState *cpu;<br>
&gt;<br>
&gt;       QEMU_LOCK_GUARD(&amp;plugin.lock);<br>
&gt;<br>
&gt;  @@ -514,6 +516,11 @@ void qemu_plugin_user_exit(void)<br>
&gt;       }<br>
&gt;<br>
&gt;       tb_flush(current_cpu);<br>
&gt;  +<br>
&gt;  +    CPU_FOREACH(cpu) {<br>
&gt;  +        qemu_plugin_disable_mem_helpers(cpu);<br>
&gt;  +    }<br>
&gt;  +<br>
&gt;       end_exclusive();<br>
&gt;<br>
&gt;       /* now it&#39;s safe to handle the exit case */<br>
&gt;  --8&lt;---------------cut here---------------end---------------&gt;8---<br>
&gt;<br>
&gt; I think both of these are find from a bsd-user point of view.<br>
<br>
Acked-by: or Reviewed-by:?<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">Sorry I wasn&#39;t clear.</div><div dir="auto"><br></div><div dir="auto">Acked-by: Warner Losh &lt;<a href="mailto:imp@bsdimp.com">imp@bsdimp.com</a>&gt;</div><div dir="auto"><br></div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

<br>
-- <br>
Alex Bennée<br>
</blockquote></div></div></div>
Mahmoud Mandour July 20, 2021, 12:28 p.m. UTC | #5
On Mon, Jul 19, 2021, 14:37 Alex Bennée <alex.bennee@linaro.org> wrote:

> In user-mode emulation there is a small race between preexit_cleanup

> and exit_group() which means we may end up calling instrumented

> instructions before the kernel reaps child threads. To solve this we

> implement a new helper which ensures the callbacks are flushed along

> with any translations before we let the host do it's a thing.

>

> While we are at it make the documentation of

> qemu_plugin_register_atexit_cb clearer as to what the user can expect.

>

> Signed-off-by: Alex Bennée <alex.bennee@linaro.org>

>



Looks great to me and I cannot reproduce the race with it installed in.

With calling `qemu_plugin_disable_mem_helpers`:

Reviewed-by: Mahmoud Mandour <ma.mandourr@gmail.com>


---
>  include/qemu/plugin.h      | 12 ++++++++++++

>  include/qemu/qemu-plugin.h | 13 +++++++++++++

>  bsd-user/syscall.c         |  6 +++---

>  linux-user/exit.c          |  2 +-

>  plugins/core.c             | 33 +++++++++++++++++++++++++++++++++

>  5 files changed, 62 insertions(+), 4 deletions(-)

>

> diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h

> index 0fefbc6084..9a8438f683 100644

> --- a/include/qemu/plugin.h

> +++ b/include/qemu/plugin.h

> @@ -190,6 +190,16 @@ void qemu_plugin_add_dyn_cb_arr(GArray *arr);

>

>  void qemu_plugin_disable_mem_helpers(CPUState *cpu);

>

> +/**

> + * qemu_plugin_user_exit(): clean-up callbacks before calling exit

> callbacks

> + *

> + * This is a user-mode only helper that ensure we have fully cleared

> + * callbacks from all threads before calling the exit callbacks. This

> + * is so the plugins themselves don't have to jump through hoops to

> + * guard against race conditions.

> + */

> +void qemu_plugin_user_exit(void);

> +

>  #else /* !CONFIG_PLUGIN */

>

>  static inline void qemu_plugin_add_opts(void)

> @@ -250,6 +260,8 @@ void qemu_plugin_add_dyn_cb_arr(GArray *arr)

>  static inline void qemu_plugin_disable_mem_helpers(CPUState *cpu)

>  { }

>

> +static inline void qemu_plugin_user_exit(void)

> +{ }

>  #endif /* !CONFIG_PLUGIN */

>

>  #endif /* QEMU_PLUGIN_H */

> diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h

> index dc3496f36c..e6e815abc5 100644

> --- a/include/qemu/qemu-plugin.h

> +++ b/include/qemu/qemu-plugin.h

> @@ -549,6 +549,19 @@ void qemu_plugin_vcpu_for_each(qemu_plugin_id_t id,

>  void qemu_plugin_register_flush_cb(qemu_plugin_id_t id,

>                                     qemu_plugin_simple_cb_t cb);

>

> +/**

> + * qemu_plugin_register_atexit_cb() - register exit callback

> + * @id: plugin ID

> + * @cb: callback

> + * @userdata: user data for callback

> + *

> + * The @cb function is called once execution has finished. Plugins

> + * should be able to free all their resources at this point much like

> + * after a reset/uninstall callback is called.

> + *

> + * In user-mode it is possible a few un-instrumented instructions from

> + * child threads may run before the host kernel reaps the threads.

> + */

>  void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,

>                                      qemu_plugin_udata_cb_t cb, void

> *userdata);

>

> diff --git a/bsd-user/syscall.c b/bsd-user/syscall.c

> index 7d986e9700..3f44311396 100644

> --- a/bsd-user/syscall.c

> +++ b/bsd-user/syscall.c

> @@ -335,7 +335,7 @@ abi_long do_freebsd_syscall(void *cpu_env, int num,

> abi_long arg1,

>          _mcleanup();

>  #endif

>          gdb_exit(arg1);

> -        qemu_plugin_atexit_cb();

> +        qemu_plugin_user_exit();

>          /* XXX: should free thread stack and CPU env */

>          _exit(arg1);

>          ret = 0; /* avoid warning */

> @@ -437,7 +437,7 @@ abi_long do_netbsd_syscall(void *cpu_env, int num,

> abi_long arg1,

>          _mcleanup();

>  #endif

>          gdb_exit(arg1);

> -        qemu_plugin_atexit_cb();

> +        qemu_plugin_user_exit();

>          /* XXX: should free thread stack and CPU env */

>          _exit(arg1);

>          ret = 0; /* avoid warning */

> @@ -516,7 +516,7 @@ abi_long do_openbsd_syscall(void *cpu_env, int num,

> abi_long arg1,

>          _mcleanup();

>  #endif

>          gdb_exit(arg1);

> -        qemu_plugin_atexit_cb();

> +        qemu_plugin_user_exit();

>          /* XXX: should free thread stack and CPU env */

>          _exit(arg1);

>          ret = 0; /* avoid warning */

> diff --git a/linux-user/exit.c b/linux-user/exit.c

> index 70b344048c..527e29cbc1 100644

> --- a/linux-user/exit.c

> +++ b/linux-user/exit.c

> @@ -35,5 +35,5 @@ void preexit_cleanup(CPUArchState *env, int code)

>          __gcov_dump();

>  #endif

>          gdb_exit(code);

> -        qemu_plugin_atexit_cb();

> +        qemu_plugin_user_exit();

>  }

> diff --git a/plugins/core.c b/plugins/core.c

> index e1bcdb570d..c573b81a96 100644

> --- a/plugins/core.c

> +++ b/plugins/core.c

> @@ -487,6 +487,39 @@ void qemu_plugin_register_atexit_cb(qemu_plugin_id_t

> id,

>      plugin_register_cb_udata(id, QEMU_PLUGIN_EV_ATEXIT, cb, udata);

>  }

>

> +/*

> + * Handle exit from linux-user. Unlike the normal atexit() mechanism

> + * we need to handle the clean-up manually as it's possible threads

> + * are still running. We need to remove all callbacks from code

> + * generation, flush the current translations and then we can safely

> + * trigger the exit callbacks.

> + */

> +

> +void qemu_plugin_user_exit(void)

> +{

> +    enum qemu_plugin_event ev;

> +

> +    QEMU_LOCK_GUARD(&plugin.lock);

> +

> +    start_exclusive();

> +

> +    /* un-register all callbacks except the final AT_EXIT one */

> +    for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) {

> +        if (ev != QEMU_PLUGIN_EV_ATEXIT) {

> +            struct qemu_plugin_ctx *ctx;

> +            QTAILQ_FOREACH(ctx, &plugin.ctxs, entry) {

> +                plugin_unregister_cb__locked(ctx, ev);

> +            }

> +        }

> +    }

> +

> +    tb_flush(current_cpu);

> +    end_exclusive();

> +

> +    /* now it's safe to handle the exit case */

> +    qemu_plugin_atexit_cb();

> +}

> +

>  /*

>   * Call this function after longjmp'ing to the main loop. It's possible

> that the

>   * last instruction of a TB might have used helpers, and therefore the

> --

> 2.32.0.264.g75ae10bc75

>

>
<div dir="auto"><div><br><br><div class="gmail_quote"><div dir="ltr">On Mon, Jul 19, 2021, 14:37 Alex Bennée &lt;<a href="mailto:alex.bennee@linaro.org">alex.bennee@linaro.org</a>&gt; wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">In user-mode emulation there is a small race between preexit_cleanup<br>
and exit_group() which means we may end up calling instrumented<br>
instructions before the kernel reaps child threads. To solve this we<br>
implement a new helper which ensures the callbacks are flushed along<br>
with any translations before we let the host do it&#39;s a thing.<br>
<br>
While we are at it make the documentation of<br>
qemu_plugin_register_atexit_cb clearer as to what the user can expect.<br>
<br>
Signed-off-by: Alex Bennée &lt;<a href="mailto:alex.bennee@linaro.org" target="_blank" rel="noreferrer">alex.bennee@linaro.org</a>&gt;<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto"><br></div><div dir="auto">Looks great to me and I cannot reproduce the race with it installed in.</div><div dir="auto"><br></div><div dir="auto">With calling `qemu_plugin_disable_mem_helpers`:</div><div dir="auto"><br></div><div dir="auto">Reviewed-by: Mahmoud Mandour &lt;<a href="mailto:ma.mandourr@gmail.com">ma.mandourr@gmail.com</a>&gt;</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

---<br>
 include/qemu/plugin.h      | 12 ++++++++++++<br>
 include/qemu/qemu-plugin.h | 13 +++++++++++++<br>
 bsd-user/syscall.c         |  6 +++---<br>
 linux-user/exit.c          |  2 +-<br>
 plugins/core.c             | 33 +++++++++++++++++++++++++++++++++<br>
 5 files changed, 62 insertions(+), 4 deletions(-)<br>
<br>
diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h<br>
index 0fefbc6084..9a8438f683 100644<br>
--- a/include/qemu/plugin.h<br>
+++ b/include/qemu/plugin.h<br>
@@ -190,6 +190,16 @@ void qemu_plugin_add_dyn_cb_arr(GArray *arr);<br>
<br>
 void qemu_plugin_disable_mem_helpers(CPUState *cpu);<br>
<br>
+/**<br>
+ * qemu_plugin_user_exit(): clean-up callbacks before calling exit callbacks<br>
+ *<br>
+ * This is a user-mode only helper that ensure we have fully cleared<br>
+ * callbacks from all threads before calling the exit callbacks. This<br>
+ * is so the plugins themselves don&#39;t have to jump through hoops to<br>
+ * guard against race conditions.<br>
+ */<br>
+void qemu_plugin_user_exit(void);<br>
+<br>
 #else /* !CONFIG_PLUGIN */<br>
<br>
 static inline void qemu_plugin_add_opts(void)<br>
@@ -250,6 +260,8 @@ void qemu_plugin_add_dyn_cb_arr(GArray *arr)<br>
 static inline void qemu_plugin_disable_mem_helpers(CPUState *cpu)<br>
 { }<br>
<br>
+static inline void qemu_plugin_user_exit(void)<br>
+{ }<br>
 #endif /* !CONFIG_PLUGIN */<br>
<br>
 #endif /* QEMU_PLUGIN_H */<br>
diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h<br>
index dc3496f36c..e6e815abc5 100644<br>
--- a/include/qemu/qemu-plugin.h<br>
+++ b/include/qemu/qemu-plugin.h<br>
@@ -549,6 +549,19 @@ void qemu_plugin_vcpu_for_each(qemu_plugin_id_t id,<br>
 void qemu_plugin_register_flush_cb(qemu_plugin_id_t id,<br>
                                    qemu_plugin_simple_cb_t cb);<br>
<br>
+/**<br>
+ * qemu_plugin_register_atexit_cb() - register exit callback<br>
+ * @id: plugin ID<br>
+ * @cb: callback<br>
+ * @userdata: user data for callback<br>
+ *<br>
+ * The @cb function is called once execution has finished. Plugins<br>
+ * should be able to free all their resources at this point much like<br>
+ * after a reset/uninstall callback is called.<br>
+ *<br>
+ * In user-mode it is possible a few un-instrumented instructions from<br>
+ * child threads may run before the host kernel reaps the threads.<br>
+ */<br>
 void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,<br>
                                     qemu_plugin_udata_cb_t cb, void *userdata);<br>
<br>
diff --git a/bsd-user/syscall.c b/bsd-user/syscall.c<br>
index 7d986e9700..3f44311396 100644<br>
--- a/bsd-user/syscall.c<br>
+++ b/bsd-user/syscall.c<br>
@@ -335,7 +335,7 @@ abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1,<br>
         _mcleanup();<br>
 #endif<br>
         gdb_exit(arg1);<br>
-        qemu_plugin_atexit_cb();<br>
+        qemu_plugin_user_exit();<br>
         /* XXX: should free thread stack and CPU env */<br>
         _exit(arg1);<br>
         ret = 0; /* avoid warning */<br>
@@ -437,7 +437,7 @@ abi_long do_netbsd_syscall(void *cpu_env, int num, abi_long arg1,<br>
         _mcleanup();<br>
 #endif<br>
         gdb_exit(arg1);<br>
-        qemu_plugin_atexit_cb();<br>
+        qemu_plugin_user_exit();<br>
         /* XXX: should free thread stack and CPU env */<br>
         _exit(arg1);<br>
         ret = 0; /* avoid warning */<br>
@@ -516,7 +516,7 @@ abi_long do_openbsd_syscall(void *cpu_env, int num, abi_long arg1,<br>
         _mcleanup();<br>
 #endif<br>
         gdb_exit(arg1);<br>
-        qemu_plugin_atexit_cb();<br>
+        qemu_plugin_user_exit();<br>
         /* XXX: should free thread stack and CPU env */<br>
         _exit(arg1);<br>
         ret = 0; /* avoid warning */<br>
diff --git a/linux-user/exit.c b/linux-user/exit.c<br>
index 70b344048c..527e29cbc1 100644<br>
--- a/linux-user/exit.c<br>
+++ b/linux-user/exit.c<br>
@@ -35,5 +35,5 @@ void preexit_cleanup(CPUArchState *env, int code)<br>
         __gcov_dump();<br>
 #endif<br>
         gdb_exit(code);<br>
-        qemu_plugin_atexit_cb();<br>
+        qemu_plugin_user_exit();<br>
 }<br>
diff --git a/plugins/core.c b/plugins/core.c<br>
index e1bcdb570d..c573b81a96 100644<br>
--- a/plugins/core.c<br>
+++ b/plugins/core.c<br>
@@ -487,6 +487,39 @@ void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,<br>
     plugin_register_cb_udata(id, QEMU_PLUGIN_EV_ATEXIT, cb, udata);<br>
 }<br>
<br>
+/*<br>
+ * Handle exit from linux-user. Unlike the normal atexit() mechanism<br>
+ * we need to handle the clean-up manually as it&#39;s possible threads<br>
+ * are still running. We need to remove all callbacks from code<br>
+ * generation, flush the current translations and then we can safely<br>
+ * trigger the exit callbacks.<br>
+ */<br>
+<br>
+void qemu_plugin_user_exit(void)<br>
+{<br>
+    enum qemu_plugin_event ev;<br>
+<br>
+    QEMU_LOCK_GUARD(&amp;plugin.lock);<br>
+<br>
+    start_exclusive();<br>
+<br>
+    /* un-register all callbacks except the final AT_EXIT one */<br>
+    for (ev = 0; ev &lt; QEMU_PLUGIN_EV_MAX; ev++) {<br>
+        if (ev != QEMU_PLUGIN_EV_ATEXIT) {<br>
+            struct qemu_plugin_ctx *ctx;<br>
+            QTAILQ_FOREACH(ctx, &amp;plugin.ctxs, entry) {<br>
+                plugin_unregister_cb__locked(ctx, ev);<br>
+            }<br>
+        }<br>
+    }<br>
+<br>
+    tb_flush(current_cpu);<br>
+    end_exclusive();<br>
+<br>
+    /* now it&#39;s safe to handle the exit case */<br>
+    qemu_plugin_atexit_cb();<br>
+}<br>
+<br>
 /*<br>
  * Call this function after longjmp&#39;ing to the main loop. It&#39;s possible that the<br>
  * last instruction of a TB might have used helpers, and therefore the<br>
-- <br>
2.32.0.264.g75ae10bc75<br>
<br>
</blockquote></div></div></div>
diff mbox series

Patch

diff --git a/include/qemu/plugin.h b/include/qemu/plugin.h
index 0fefbc6084..9a8438f683 100644
--- a/include/qemu/plugin.h
+++ b/include/qemu/plugin.h
@@ -190,6 +190,16 @@  void qemu_plugin_add_dyn_cb_arr(GArray *arr);
 
 void qemu_plugin_disable_mem_helpers(CPUState *cpu);
 
+/**
+ * qemu_plugin_user_exit(): clean-up callbacks before calling exit callbacks
+ *
+ * This is a user-mode only helper that ensure we have fully cleared
+ * callbacks from all threads before calling the exit callbacks. This
+ * is so the plugins themselves don't have to jump through hoops to
+ * guard against race conditions.
+ */
+void qemu_plugin_user_exit(void);
+
 #else /* !CONFIG_PLUGIN */
 
 static inline void qemu_plugin_add_opts(void)
@@ -250,6 +260,8 @@  void qemu_plugin_add_dyn_cb_arr(GArray *arr)
 static inline void qemu_plugin_disable_mem_helpers(CPUState *cpu)
 { }
 
+static inline void qemu_plugin_user_exit(void)
+{ }
 #endif /* !CONFIG_PLUGIN */
 
 #endif /* QEMU_PLUGIN_H */
diff --git a/include/qemu/qemu-plugin.h b/include/qemu/qemu-plugin.h
index dc3496f36c..e6e815abc5 100644
--- a/include/qemu/qemu-plugin.h
+++ b/include/qemu/qemu-plugin.h
@@ -549,6 +549,19 @@  void qemu_plugin_vcpu_for_each(qemu_plugin_id_t id,
 void qemu_plugin_register_flush_cb(qemu_plugin_id_t id,
                                    qemu_plugin_simple_cb_t cb);
 
+/**
+ * qemu_plugin_register_atexit_cb() - register exit callback
+ * @id: plugin ID
+ * @cb: callback
+ * @userdata: user data for callback
+ *
+ * The @cb function is called once execution has finished. Plugins
+ * should be able to free all their resources at this point much like
+ * after a reset/uninstall callback is called.
+ *
+ * In user-mode it is possible a few un-instrumented instructions from
+ * child threads may run before the host kernel reaps the threads.
+ */
 void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,
                                     qemu_plugin_udata_cb_t cb, void *userdata);
 
diff --git a/bsd-user/syscall.c b/bsd-user/syscall.c
index 7d986e9700..3f44311396 100644
--- a/bsd-user/syscall.c
+++ b/bsd-user/syscall.c
@@ -335,7 +335,7 @@  abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1,
         _mcleanup();
 #endif
         gdb_exit(arg1);
-        qemu_plugin_atexit_cb();
+        qemu_plugin_user_exit();
         /* XXX: should free thread stack and CPU env */
         _exit(arg1);
         ret = 0; /* avoid warning */
@@ -437,7 +437,7 @@  abi_long do_netbsd_syscall(void *cpu_env, int num, abi_long arg1,
         _mcleanup();
 #endif
         gdb_exit(arg1);
-        qemu_plugin_atexit_cb();
+        qemu_plugin_user_exit();
         /* XXX: should free thread stack and CPU env */
         _exit(arg1);
         ret = 0; /* avoid warning */
@@ -516,7 +516,7 @@  abi_long do_openbsd_syscall(void *cpu_env, int num, abi_long arg1,
         _mcleanup();
 #endif
         gdb_exit(arg1);
-        qemu_plugin_atexit_cb();
+        qemu_plugin_user_exit();
         /* XXX: should free thread stack and CPU env */
         _exit(arg1);
         ret = 0; /* avoid warning */
diff --git a/linux-user/exit.c b/linux-user/exit.c
index 70b344048c..527e29cbc1 100644
--- a/linux-user/exit.c
+++ b/linux-user/exit.c
@@ -35,5 +35,5 @@  void preexit_cleanup(CPUArchState *env, int code)
         __gcov_dump();
 #endif
         gdb_exit(code);
-        qemu_plugin_atexit_cb();
+        qemu_plugin_user_exit();
 }
diff --git a/plugins/core.c b/plugins/core.c
index e1bcdb570d..c573b81a96 100644
--- a/plugins/core.c
+++ b/plugins/core.c
@@ -487,6 +487,39 @@  void qemu_plugin_register_atexit_cb(qemu_plugin_id_t id,
     plugin_register_cb_udata(id, QEMU_PLUGIN_EV_ATEXIT, cb, udata);
 }
 
+/*
+ * Handle exit from linux-user. Unlike the normal atexit() mechanism
+ * we need to handle the clean-up manually as it's possible threads
+ * are still running. We need to remove all callbacks from code
+ * generation, flush the current translations and then we can safely
+ * trigger the exit callbacks.
+ */
+
+void qemu_plugin_user_exit(void)
+{
+    enum qemu_plugin_event ev;
+
+    QEMU_LOCK_GUARD(&plugin.lock);
+
+    start_exclusive();
+
+    /* un-register all callbacks except the final AT_EXIT one */
+    for (ev = 0; ev < QEMU_PLUGIN_EV_MAX; ev++) {
+        if (ev != QEMU_PLUGIN_EV_ATEXIT) {
+            struct qemu_plugin_ctx *ctx;
+            QTAILQ_FOREACH(ctx, &plugin.ctxs, entry) {
+                plugin_unregister_cb__locked(ctx, ev);
+            }
+        }
+    }
+
+    tb_flush(current_cpu);
+    end_exclusive();
+
+    /* now it's safe to handle the exit case */
+    qemu_plugin_atexit_cb();
+}
+
 /*
  * Call this function after longjmp'ing to the main loop. It's possible that the
  * last instruction of a TB might have used helpers, and therefore the