tracing/seq-buf/printk: New seq-buf utility from trace_seq for printk in NMI
Subsystems (5)
Steven Rostedt
maintainercover tracing/seq-buf/printk: New seq-buf utility from trace_seq for printk in NMI
I created a separate branch for this change (although it's still going into my for-next branch which ends up in linux-next. But that branch is made up of multiple branches).
I'm hoping that it makes it into 3.19, but I'll give that choice to Linus. This is based off of a point of my ftrace/core branch which is my normal branch. But this one is separate because it may not be accepted. I'll just have to wait and see. New work for tracing will not be based on this branch.
Although Linus may not accept it, I still want it in linux-next for further testing.
-- Steve
git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-trace.git
for-next
Head SHA1: a9edc88093287183ac934be44f295f183b2c62dd
Steven Rostedt (Red Hat) (15):
tracing: Create seq_buf layer in trace_seq
tracing: Convert seq_buf_path() to be like seq_path()
tracing: Convert seq_buf fields to be like seq_file fields
tracing: Add a seq_buf_clear() helper and clear len and readpos in init
seq_buf: Create seq_buf_used() to find out how much was written
tracing: Clean up tracing_fill_pipe_page()
tracing: Use trace_seq_used() and seq_buf_used() instead of len
tracing: Add paranoid size check in trace_printk_seq()
seq_buf: Add seq_buf_can_fit() helper function
tracing: Have seq_buf use full buffer
tracing: Add seq_buf_get_buf() and seq_buf_commit() helper functions
seq-buf: Make seq_buf_bprintf() conditional on CONFIG_BINARY_PRINTF
seq_buf: Move the seq_buf code to lib/
printk: Add per_cpu printk func to allow printk to be diverted x86/nmi: Perform a safe NMI stack trace on all CPUs
----
arch/x86/kernel/apic/hw_nmi.c | 91 ++++++++- include/linux/percpu.h | 3 + include/linux/printk.h | 2 + include/linux/seq_buf.h | 136 +++++++++++++ include/linux/trace_seq.h | 30 ++- kernel/printk/printk.c | 38 +++- kernel/trace/trace.c | 65 +++++-- kernel/trace/trace_events.c | 9 +- kernel/trace/trace_functions_graph.c | 11 +- kernel/trace/trace_seq.c | 177 ++++++++--------- lib/Makefile | 2 +- lib/seq_buf.c | 359 +++++++++++++++++++++++++++++++++++ 12 files changed, 779 insertions(+), 144 deletions(-)
Steven Rostedt
maintainerPATCH 1/15 tracing: Create seq_buf layer in trace_seq
From: "Steven Rostedt (Red Hat)" <[email protected]>
Create a seq_buf layer that trace_seq sits on. The seq_buf will not be limited to page size. This will allow other usages of seq_buf instead of a hard set PAGE_SIZE one that trace_seq has.
--- include/linux/seq_buf.h | 81 +++++++++ include/linux/trace_seq.h | 12 +- kernel/trace/Makefile | 1 + kernel/trace/seq_buf.c | 341 +++++++++++++++++++++++++++++++++++ kernel/trace/trace.c | 39 ++-- kernel/trace/trace_events.c | 6 +- kernel/trace/trace_functions_graph.c | 6 +- kernel/trace/trace_seq.c | 178 +++++++++--------- 8 files changed, 538 insertions(+), 126 deletions(-) create mode 100644 include/linux/seq_buf.h create mode 100644 kernel/trace/seq_buf.c
include/linux/seq_buf.h new file +81 -0
@@ -0,0 +1,81 @@ +#ifndef _LINUX_SEQ_BUF_H +#define _LINUX_SEQ_BUF_H + +#include <linux/fs.h> + +/* +* Trace sequences are used to allow a function to call several other functions +* to create a string of data to use. +*/ + +/** +* seq_buf - seq buffer structure +* @buffer: pointer to the buffer +* @size: size of the buffer +* @len: the amount of data inside the buffer +* @readpos: The next position to read in the buffer. +*/ +struct seq_buf { +unsigned char *buffer; +unsigned int size; +unsigned int len; +unsigned int readpos; +}; + +static inline void +seq_buf_init (struct seq_buf *s,unsigned char *buf,unsigned int size) +{ + s->buffer = buf; + s->size = size; + s->len =0 ; + s->readpos =0 ; +} + +/* +* seq_buf have a buffer that might overflow. When this happens +* the len and size are set to be equal. +*/ +static inline bool +seq_buf_has_overflowed (struct seq_buf *s) +{ +return s->len == s->size; +} + +static inline void +seq_buf_set_overflow (struct seq_buf *s) +{ + s->len = s->size; +} + +/* +* How much buffer is left on the seq_buf? +*/ +static inline unsigned int +seq_buf_buffer_left (struct seq_buf *s) +{ +if (seq_buf_has_overflowed (s)) +return 0 ; + +return (s->size -1 ) - s->len; +} + +extern __printf(2, 3) +int seq_buf_printf(struct seq_buf *s, const char *fmt, ...); +extern __printf(2, 0) +int seq_buf_vprintf(struct seq_buf *s, const char *fmt, va_list args); +extern int +seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary); +extern int seq_buf_print_seq(struct seq_file *m, struct seq_buf *s); +extern int seq_buf_to_user(struct seq_buf *s, char __user *ubuf, + int cnt); +extern int seq_buf_puts(struct seq_buf *s, const char *str); +extern int seq_buf_putc(struct seq_buf *s, unsigned char c); +extern int seq_buf_putmem(struct seq_buf *s, const void *mem, unsigned int len); +extern int seq_buf_putmem_hex(struct seq_buf *s, const void *mem, + unsigned int len); +extern int seq_buf_path(struct seq_buf *s, const struct path *path); + +extern int seq_buf_bitmask(struct seq_buf *s, const unsigned long *maskp, + int nmaskbits); + +#endif /* _LINUX_SEQ_BUF_H */
include/linux/trace_seq.h +5 -7
@@ -1,7 +1,7 @@#ifndef _LINUX_TRACE_SEQ_H#define _LINUX_TRACE_SEQ_H -#include <linux/fs.h> +#include <linux/seq_buf.h> #include <asm/page.h>
@@ -12,16 +12,14 @@struct trace_seq {unsigned char buffer[PAGE_SIZE ]; -unsigned int len; -unsigned int readpos; +struct seq_buf seq;int full; };static inline void trace_seq_init (struct trace_seq *s) { - s->len =0 ; - s->readpos =0 ; +seq_buf_init (&s->seq, s->buffer,PAGE_SIZE ); s->full =0 ; }
@@ -37,7 +35,7 @@ trace_seq_init(struct trace_seq *s)static inline unsigned char *trace_seq_buffer_ptr (struct trace_seq *s) { -return s->buffer + s->len; +return s->buffer + s->seq.len; }/**
@@ -49,7 +47,7 @@ trace_seq_buffer_ptr(struct trace_seq *s)*/ static inline bool trace_seq_has_overflowed (struct trace_seq *s) { -return s->full || s->len >PAGE_SIZE -1 ; +return s->full ||seq_buf_has_overflowed (&s->seq); }/*
kernel/trace/Makefile +1 -0
@@ -29,6 +29,7 @@ obj-$(CONFIG_RING_BUFFER_BENCHMARK) += ring_buffer_benchmark.o obj-$(CONFIG_TRACING) += trace.o obj-$(CONFIG_TRACING) += trace_output.o obj-$(CONFIG_TRACING) += trace_seq.o +obj-$(CONFIG_TRACING) += seq_buf.o obj-$(CONFIG_TRACING) += trace_stat.o obj-$(CONFIG_TRACING) += trace_printk.o obj-$(CONFIG_CONTEXT_SWITCH_TRACER) += trace_sched_switch.o
kernel/trace/seq_buf.c new file +341 -0
341 lines not on this page · raw message
kernel/trace/trace.c +21 -18
@@ -939,19 +939,20 @@ out:return ret; } +/* TODO add a seq_buf_to_buffer() */ static ssize_t trace_seq_to_buffer (struct trace_seq *s,void *buf,size_t cnt) {int len; -if (s->len <= s->readpos) +if (s->seq.len <= s->seq.readpos)return -EBUSY ; - len = s->len - s->readpos; + len = s->seq.len - s->seq.readpos;if (cnt > len) cnt = len; -memcpy (buf, s->buffer + s->readpos, cnt); +memcpy (buf, s->buffer + s->seq.readpos, cnt); - s->readpos += cnt; + s->seq.readpos += cnt;return cnt; }
@@ -4315,6 +4316,8 @@ static int tracing_open_pipe(struct inode *inode, struct file *filp) goto out; } +trace_seq_init (&iter->seq); +/* * We make a copy of the current tracer to avoid concurrent * changes on it while we are reading.
@@ -4511,18 +4514,18 @@ waitagain:trace_access_lock (iter->cpu_file);while (trace_find_next_entry_inc (iter) !=NULL ) {enum print_line_t ret; -int len = iter->seq.len; +int len = iter->seq.seq.len; ret =print_trace_line (iter);if (ret ==TRACE_TYPE_PARTIAL_LINE ) {/* don't print partial lines */ - iter->seq.len = len; + iter->seq.seq.len = len;break ; }if (ret !=TRACE_TYPE_NO_CONSUME )trace_consume (iter); -if (iter->seq.len >= cnt) +if (iter->seq.seq.len >= cnt)break ;/*
@@ -4538,7 +4541,7 @@ waitagain:/* Now copy what we have to the user */ sret =trace_seq_to_user (&iter->seq, ubuf, cnt); -if (iter->seq.readpos >= iter->seq.len) +if (iter->seq.seq.readpos >= iter->seq.seq.len)trace_seq_init (&iter->seq);/*
@@ -4576,16 +4579,16 @@ tracing_fill_pipe_page(size_t rem, struct trace_iterator *iter)/* Seq buffer is page-sized, exactly what we need. */ for (;;) { - count = iter->seq.len; + count = iter->seq.seq.len; ret =print_trace_line (iter); - count = iter->seq.len - count; + count = iter->seq.seq.len - count;if (rem < count) { rem =0 ; - iter->seq.len -= count; + iter->seq.seq.len -= count;break ; }if (ret ==TRACE_TYPE_PARTIAL_LINE ) { - iter->seq.len -= count; + iter->seq.seq.len -= count;break ; }
@@ -4666,13 +4669,13 @@ static ssize_t tracing_splice_read_pipe(struct file *filp,/* Copy the data into the page, so we can start over. */ ret =trace_seq_to_buffer (&iter->seq,page_address (spd.pages[i]), - iter->seq.len); + iter->seq.seq.len);if (ret <0 ) {__free_page (spd.pages[i]);break ; } spd.partial[i].offset =0 ; - spd.partial[i].len = iter->seq.len; + spd.partial[i].len = iter->seq.seq.len;trace_seq_init (&iter->seq); }
@@ -5673,7 +5676,7 @@ tracing_stats_read(struct file *filp, char __user *ubuf, cnt =ring_buffer_read_events_cpu (trace_buf->buffer, cpu);trace_seq_printf (s,"read events: %ld\n" , cnt); - count =simple_read_from_buffer (ubuf, count, ppos, s->buffer, s->len); + count =simple_read_from_buffer (ubuf, count, ppos, s->buffer, s->seq.len);kfree (s);
@@ -6636,11 +6639,11 @@ void trace_printk_seq(struct trace_seq *s) {/* Probably should print a warning here. */ -if (s->len >=TRACE_MAX_PRINT ) - s->len =TRACE_MAX_PRINT ; +if (s->seq.len >=TRACE_MAX_PRINT ) + s->seq.len =TRACE_MAX_PRINT ;/* should be zero ended, but we are paranoid. */ - s->buffer[s->len] =0 ; + s->buffer[s->seq.len] =0 ;printk (KERN_TRACE "%s" , s->buffer);
kernel/trace/trace_events.c +3 -3
@@ -1044,7 +1044,7 @@ event_filter_read(struct file *filp, char __user *ubuf, size_t cnt,mutex_unlock (&event_mutex);if (file) - r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->len); + r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->seq.len);kfree (s);
@@ -1210,7 +1210,7 @@ subsystem_filter_read(struct file *filp, char __user *ubuf, size_t cnt,trace_seq_init (s);print_subsystem_event_filter (system, s); - r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->len); + r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->seq.len);kfree (s);
@@ -1265,7 +1265,7 @@ show_header(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)trace_seq_init (s);func (s); - r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->len); + r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->seq.len);kfree (s);
kernel/trace/trace_functions_graph.c +3 -3
@@ -1154,9 +1154,9 @@ print_graph_comment(struct trace_seq *s, struct trace_entry *ent, }/* Strip ending newline */ -if (s->buffer[s->len -1 ] =='\n' ) { - s->buffer[s->len -1 ] ='\0' ; - s->len--; +if (s->buffer[s->seq.len -1 ] =='\n' ) { + s->buffer[s->seq.len -1 ] ='\0' ; + s->seq.len--; }trace_seq_puts (s," */\n" );
kernel/trace/trace_seq.c +83 -95
314 lines not on this page · raw message
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 2/15 tracing: Convert seq_buf_path() to be like seq_path()
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
Rewrite seq_buf_path() like it is done in seq_path() and allow it to accept any escape character instead of just "\n".
Making seq_buf_path() like seq_path() will help prevent problems when converting seq_file to use the seq_buf logic.
--- include/linux/seq_buf.h | 2 +- kernel/trace/seq_buf.c | 28 ++++++++++++++++------------ kernel/trace/trace_seq.c | 5 ++--- 3 files changed, 19 insertions(+), 16 deletions(-)
include/linux/seq_buf.h +1 -1
@@ -73,7 +73,7 @@ extern int seq_buf_putc(struct seq_buf *s, unsigned char c);extern int seq_buf_putmem (struct seq_buf *s,const void *mem,unsigned int len);extern int seq_buf_putmem_hex (struct seq_buf *s,const void *mem,unsigned int len); -extern int seq_buf_path (struct seq_buf *s,const struct path *path); +extern int seq_buf_path (struct seq_buf *s,const struct path *path,const char *esc);extern int seq_buf_bitmask (struct seq_buf *s,const unsigned long *maskp,int nmaskbits);
kernel/trace/seq_buf.c +16 -12
@@ -272,28 +272,32 @@ int seq_buf_putmem_hex(struct seq_buf *s, const void *mem,* seq_buf_path - copy a path into the sequence buffer * @s: seq_buf descriptor * @path: path to write into the sequence buffer. +* @esc: set of characters to escape in the output * * Write a path name into the sequence buffer. * -* Returns zero on success, -1 on overflow +* Returns the number of written bytes on success, -1 on overflow */ -int seq_buf_path (struct seq_buf *s,const struct path *path) +int seq_buf_path (struct seq_buf *s,const struct path *path,const char *esc) { -unsigned int len =seq_buf_buffer_left (s); -unsigned char *p; +char *buf = s->buffer + s->len; +size_t size =seq_buf_buffer_left (s); +int res =-1 ;WARN_ON (s->size ==0 ); - p =d_path (path, s->buffer + s->len, len); -if (!IS_ERR (p)) { - p =mangle_path (s->buffer + s->len, p,"\n" ); -if (p) { - s->len = p - s->buffer; -return 0 ; +if (size) { +char *p =d_path (path, buf, size); +if (!IS_ERR (p)) { +char *end =mangle_path (buf, p, esc); +if (end) + res = end - buf; } } -seq_buf_set_overflow (s); -return -1 ; +if (res >0 ) + s->len += res; + +return res; }/**
kernel/trace/trace_seq.c +2 -3
@@ -326,7 +326,6 @@ EXPORT_SYMBOL_GPL(trace_seq_putmem_hex);int trace_seq_path (struct trace_seq *s,const struct path *path) {unsigned int save_len = s->seq.len; -int ret;if (s->full)return 0 ;
@@ -338,7 +337,7 @@ int trace_seq_path(struct trace_seq *s, const struct path *path)return 0 ; } - ret =seq_buf_path (&s->seq, path); +seq_buf_path (&s->seq, path,"\n" );if (unlikely (seq_buf_has_overflowed (&s->seq))) { s->seq.len = save_len;
@@ -346,7 +345,7 @@ int trace_seq_path(struct trace_seq *s, const struct path *path)return 0 ; } -return ret; +return 1 ; }EXPORT_SYMBOL_GPL (trace_seq_path);
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 3/15 tracing: Convert seq_buf fields to be like seq_file fields
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
In facilitating the conversion of seq_file to use seq_buf, have the seq_buf fields match the types used by seq_file.
--- include/linux/seq_buf.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
include/linux/seq_buf.h +4 -4
@@ -16,10 +16,10 @@* @readpos: The next position to read in the buffer. */ struct seq_buf { -unsigned char *buffer; -unsigned int size; -unsigned int len; -unsigned int readpos; +char *buffer; +size_t size; +size_t len; +loff_t readpos; }; static inline void
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 4/15 tracing: Add a seq_buf_clear() helper and clear len and readpos in init
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
Add a helper function seq_buf_clear() that resets the len and readpos fields of a seq_buf. Currently it is only used in the seq_buf_init() but will be used later when updating the seq_file code.
--- include/linux/seq_buf.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-)
include/linux/seq_buf.h +7 -2
@@ -22,13 +22,18 @@ struct seq_buf {loff_t readpos; }; +static inline void seq_buf_clear (struct seq_buf *s) +{ + s->len =0 ; + s->readpos =0 ; +} +static inline void seq_buf_init (struct seq_buf *s,unsigned char *buf,unsigned int size) { s->buffer = buf; s->size = size; - s->len =0 ; - s->readpos =0 ; +seq_buf_clear (s); }/*
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 5/15 seq_buf: Create seq_buf_used() to find out how much was written
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
Add a helper function seq_buf_used() that replaces the SEQ_BUF_USED() private macro to let callers have a method to know how much of the seq_buf was written to.
--- include/linux/seq_buf.h | 6 ++++++ kernel/trace/seq_buf.c | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-)
include/linux/seq_buf.h +6 -0
@@ -64,6 +64,12 @@ seq_buf_buffer_left(struct seq_buf *s)return (s->size -1 ) - s->len; } +/* How much buffer was written? */ +static inline unsigned int seq_buf_used (struct seq_buf *s) +{ +return min (s->len, s->size); +} + extern __printf(2, 3) int seq_buf_printf(struct seq_buf *s, const char *fmt, ...); extern __printf(2, 0)
kernel/trace/seq_buf.c +1 -4
@@ -16,9 +16,6 @@#include <linux/seq_file.h> #include <linux/seq_buf.h> -/* How much buffer is written? */ -#define SEQ_BUF_USED (s) min((s)->len, (s)->size - 1) -/** * seq_buf_print_seq - move the contents of seq_buf into a seq_file * @m: the seq_file descriptor that is the destination
@@ -28,7 +25,7 @@*/ int seq_buf_print_seq (struct seq_file *m,struct seq_buf *s) { -unsigned int len =SEQ_BUF_USED (s); +unsigned int len =seq_buf_used (s);return seq_write (m, s->buffer, len); }
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 6/15 tracing: Clean up tracing_fill_pipe_page()
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
The function tracing_fill_pipe_page() logic is a little confusing with the use of count saving the seq.len and reusing it.
Instead of subtracting a number that is calculated from the saved value of the seq.len from seq.len, just save the seq.len at the start and if we need to reset it, just assign it again.
When the seq_buf overflow is len == size + 1, the current logic will break. Changing it to use a saved length for resetting back to the original value is more robust and will work when we change the way seq_buf sets the overflow.
--- kernel/trace/trace.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-)
kernel/trace/trace.c +19 -6
@@ -4575,20 +4575,33 @@ static size_t tracing_fill_pipe_page(size_t rem, struct trace_iterator *iter) { size_t count; +int save_len;int ret;/* Seq buffer is page-sized, exactly what we need. */ for (;;) { - count = iter->seq.seq.len; + save_len = iter->seq.seq.len; ret =print_trace_line (iter); - count = iter->seq.seq.len - count; -if (rem < count) { - rem =0 ; - iter->seq.seq.len -= count; + +if (trace_seq_has_overflowed (&iter->seq)) { + iter->seq.seq.len = save_len;break ; } + +/* +* This should not be hit, because it should only +* be set if the iter->seq overflowed. But check it +* anyway to be safe. +*/ if (ret ==TRACE_TYPE_PARTIAL_LINE ) { - iter->seq.seq.len -= count; + iter->seq.seq.len = save_len; +break ; + } + + count = iter->seq.seq.len - save_len; +if (rem < count) { + rem =0 ; + iter->seq.seq.len = save_len;break ; }
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 7/15 tracing: Use trace_seq_used() and seq_buf_used() instead of len
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
As the seq_buf->len will soon be +1 size when there's an overflow, we must use trace_seq_used() or seq_buf_used() methods to get the real length. This will prevent buffer overflow issues if just the len of the seq_buf descriptor is used to copy memory.
--- include/linux/trace_seq.h | 20 +++++++++++++++++++- kernel/trace/seq_buf.c | 2 +- kernel/trace/trace.c | 21 +++++++++++---------- kernel/trace/trace_events.c | 9 ++++++--- kernel/trace/trace_functions_graph.c | 5 ++++- kernel/trace/trace_seq.c | 2 +- 6 files changed, 42 insertions(+), 17 deletions(-)
include/linux/trace_seq.h +19 -1
@@ -24,6 +24,24 @@ trace_seq_init(struct trace_seq *s) }/** +* trace_seq_used - amount of actual data written to buffer +* @s: trace sequence descriptor +* +* Returns the amount of data written to the buffer. +* +* IMPORTANT! +* +* Use this instead of @s->seq.len if you need to pass the amount +* of data from the buffer to another buffer (userspace, or what not). +* The @s->seq.len on overflow is bigger than the buffer size and +* using it can cause access to undefined memory. +*/ +static inline int trace_seq_used (struct trace_seq *s) +{ +return seq_buf_used (&s->seq); +} + +/** * trace_seq_buffer_ptr - return pointer to next location in buffer * @s: trace sequence descriptor *
@@ -35,7 +53,7 @@ trace_seq_init(struct trace_seq *s)static inline unsigned char *trace_seq_buffer_ptr (struct trace_seq *s) { -return s->buffer + s->seq.len; +return s->buffer +seq_buf_used (&s->seq); }/**
kernel/trace/seq_buf.c +1 -1
@@ -328,7 +328,7 @@ int seq_buf_to_user(struct seq_buf *s, char __user *ubuf, int cnt)if (s->len <= s->readpos)return -EBUSY ; - len = s->len - s->readpos; + len =seq_buf_used (s) - s->readpos;if (cnt > len) cnt = len; ret =copy_to_user (ubuf, s->buffer + s->readpos, cnt);
kernel/trace/trace.c +11 -10
@@ -944,10 +944,10 @@ static ssize_t trace_seq_to_buffer(struct trace_seq *s, void *buf, size_t cnt) {int len; -if (s->seq.len <= s->seq.readpos) +if (trace_seq_used (s) <= s->seq.readpos)return -EBUSY ; - len = s->seq.len - s->seq.readpos; + len =trace_seq_used (s) - s->seq.readpos;if (cnt > len) cnt = len;memcpy (buf, s->buffer + s->seq.readpos, cnt);
@@ -4514,18 +4514,18 @@ waitagain:trace_access_lock (iter->cpu_file);while (trace_find_next_entry_inc (iter) !=NULL ) {enum print_line_t ret; -int len = iter->seq.seq.len; +int save_len = iter->seq.seq.len; ret =print_trace_line (iter);if (ret ==TRACE_TYPE_PARTIAL_LINE ) {/* don't print partial lines */ - iter->seq.seq.len = len; + iter->seq.seq.len = save_len;break ; }if (ret !=TRACE_TYPE_NO_CONSUME )trace_consume (iter); -if (iter->seq.seq.len >= cnt) +if (trace_seq_used (&iter->seq) >= cnt)break ;/*
@@ -4541,7 +4541,7 @@ waitagain:/* Now copy what we have to the user */ sret =trace_seq_to_user (&iter->seq, ubuf, cnt); -if (iter->seq.seq.readpos >= iter->seq.seq.len) +if (iter->seq.seq.readpos >=trace_seq_used (&iter->seq))trace_seq_init (&iter->seq);/*
@@ -4598,7 +4598,7 @@ tracing_fill_pipe_page(size_t rem, struct trace_iterator *iter)break ; } - count = iter->seq.seq.len - save_len; + count =trace_seq_used (&iter->seq) - save_len;if (rem < count) { rem =0 ; iter->seq.seq.len = save_len;
@@ -4682,13 +4682,13 @@ static ssize_t tracing_splice_read_pipe(struct file *filp,/* Copy the data into the page, so we can start over. */ ret =trace_seq_to_buffer (&iter->seq,page_address (spd.pages[i]), - iter->seq.seq.len); +trace_seq_used (&iter->seq));if (ret <0 ) {__free_page (spd.pages[i]);break ; } spd.partial[i].offset =0 ; - spd.partial[i].len = iter->seq.seq.len; + spd.partial[i].len =trace_seq_used (&iter->seq);trace_seq_init (&iter->seq); }
@@ -5689,7 +5689,8 @@ tracing_stats_read(struct file *filp, char __user *ubuf, cnt =ring_buffer_read_events_cpu (trace_buf->buffer, cpu);trace_seq_printf (s,"read events: %ld\n" , cnt); - count =simple_read_from_buffer (ubuf, count, ppos, s->buffer, s->seq.len); + count =simple_read_from_buffer (ubuf, count, ppos, + s->buffer,trace_seq_used (s));kfree (s);
kernel/trace/trace_events.c +6 -3
@@ -1044,7 +1044,8 @@ event_filter_read(struct file *filp, char __user *ubuf, size_t cnt,mutex_unlock (&event_mutex);if (file) - r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->seq.len); + r =simple_read_from_buffer (ubuf, cnt, ppos, + s->buffer,trace_seq_used (s));kfree (s);
@@ -1210,7 +1211,8 @@ subsystem_filter_read(struct file *filp, char __user *ubuf, size_t cnt,trace_seq_init (s);print_subsystem_event_filter (system, s); - r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->seq.len); + r =simple_read_from_buffer (ubuf, cnt, ppos, + s->buffer,trace_seq_used (s));kfree (s);
@@ -1265,7 +1267,8 @@ show_header(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)trace_seq_init (s);func (s); - r =simple_read_from_buffer (ubuf, cnt, ppos, s->buffer, s->seq.len); + r =simple_read_from_buffer (ubuf, cnt, ppos, + s->buffer,trace_seq_used (s));kfree (s);
kernel/trace/trace_functions_graph.c +4 -1
@@ -1153,6 +1153,9 @@ print_graph_comment(struct trace_seq *s, struct trace_entry *ent,return ret; } +if (trace_seq_has_overflowed (s)) + goto out; +/* Strip ending newline */ if (s->buffer[s->seq.len -1 ] =='\n' ) { s->buffer[s->seq.len -1 ] ='\0' ;
@@ -1160,7 +1163,7 @@ print_graph_comment(struct trace_seq *s, struct trace_entry *ent, }trace_seq_puts (s," */\n" ); - + out:return trace_handle_return (s); }
kernel/trace/trace_seq.c +1 -1
@@ -30,7 +30,7 @@#define TRACE_SEQ_BUF_LEFT (s) seq_buf_buffer_left(&(s)->seq)/* How much buffer is written? */ -#define TRACE_SEQ_BUF_USED (s) min((s)->seq.len, (unsigned int)(PAGE_SIZE - 1)) +#define TRACE_SEQ_BUF_USED (s) seq_buf_used(&(s)->seq)/* * trace_seq should work with being initialized with 0s.
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 8/15 tracing: Add paranoid size check in trace_printk_seq()
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
To be really paranoid about writing out of bound data in trace_printk_seq(), add another check of len compared to size.
--- kernel/trace/trace.c | 8 ++++++++ 1 file changed, 8 insertions(+)
kernel/trace/trace.c +8 -0
@@ -6656,6 +6656,14 @@ trace_printk_seq(struct trace_seq *s)if (s->seq.len >=TRACE_MAX_PRINT ) s->seq.len =TRACE_MAX_PRINT ; +/* +* More paranoid code. Although the buffer size is set to +* PAGE_SIZE, and TRACE_MAX_PRINT is 1000, this is just +* an extra layer of protection. +*/ +if (WARN_ON_ONCE (s->seq.len >= s->seq.size)) + s->seq.len = s->seq.size -1 ; +/* should be zero ended, but we are paranoid. */ s->buffer[s->seq.len] =0 ;
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 9/15 seq_buf: Add seq_buf_can_fit() helper function
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
Add a seq_buf_can_fit() helper function that removes the possible mistakes of comparing the seq_buf length plus added data compared to the size of the buffer.
--- kernel/trace/seq_buf.c | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-)
kernel/trace/seq_buf.c +18 -5
@@ -17,6 +17,19 @@#include <linux/seq_buf.h> /** +* seq_buf_can_fit - can the new data fit in the current buffer? +* @s: the seq_buf descriptor +* @len: The length to see if it can fit in the current buffer +* +* Returns true if there's enough unused space in the seq_buf buffer +* to fit the amount of new data according to @len. +*/ +static bool seq_buf_can_fit (struct seq_buf *s,size_t len) +{ +return s->len + len < s->size; +} + +/** * seq_buf_print_seq - move the contents of seq_buf into a seq_file * @m: the seq_file descriptor that is the destination * @s: the seq_buf descriptor that is the source.
@@ -48,7 +61,7 @@ int seq_buf_vprintf(struct seq_buf *s, const char *fmt, va_list args)if (s->len < s->size) { len =vsnprintf (s->buffer + s->len, s->size - s->len, fmt, args); -if (s->len + len < s->size) { +if (seq_buf_can_fit (s, len)) { s->len += len;return 0 ; }
@@ -137,7 +150,7 @@ int seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary)if (s->len < s->size) { ret =bstr_printf (s->buffer + s->len, len, fmt, binary); -if (s->len + ret < s->size) { +if (seq_buf_can_fit (s, ret)) { s->len += ret;return 0 ; }
@@ -161,7 +174,7 @@ int seq_buf_puts(struct seq_buf *s, const char *str)WARN_ON (s->size ==0 ); -if (s->len + len < s->size) { +if (seq_buf_can_fit (s, len)) {memcpy (s->buffer + s->len, str, len); s->len += len;return 0 ;
@@ -183,7 +196,7 @@ int seq_buf_putc(struct seq_buf *s, unsigned char c) {WARN_ON (s->size ==0 ); -if (s->len +1 < s->size) { +if (seq_buf_can_fit (s,1 )) { s->buffer[s->len++] = c;return 0 ; }
@@ -207,7 +220,7 @@ int seq_buf_putmem(struct seq_buf *s, const void *mem, unsigned int len) {WARN_ON (s->size ==0 ); -if (s->len + len < s->size) { +if (seq_buf_can_fit (s, len)) {memcpy (s->buffer + s->len, mem, len); s->len += len;return 0 ;
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 10/15 tracing: Have seq_buf use full buffer
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
Currently seq_buf is full when all but one byte of the buffer is filled. Change it so that the seq_buf is full when all of the buffer is filled.
Some of the functions would fill the buffer completely and report everything was fine. This was inconsistent with the max of size - 1. Changing this to be max of size makes all functions consistent.
--- include/linux/seq_buf.h | 6 +++--- kernel/trace/seq_buf.c | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-)
include/linux/seq_buf.h +3 -3
@@ -43,13 +43,13 @@ seq_buf_init(struct seq_buf *s, unsigned char *buf, unsigned int size)static inline bool seq_buf_has_overflowed (struct seq_buf *s) { -return s->len == s->size; +return s->len > s->size; }static inline void seq_buf_set_overflow (struct seq_buf *s) { - s->len = s->size; + s->len = s->size +1 ; }/*
@@ -61,7 +61,7 @@ seq_buf_buffer_left(struct seq_buf *s)if (seq_buf_has_overflowed (s))return 0 ; -return (s->size -1 ) - s->len; +return s->size - s->len; }/* How much buffer was written? */
kernel/trace/seq_buf.c +6 -3
@@ -26,7 +26,7 @@*/ static bool seq_buf_can_fit (struct seq_buf *s,size_t len) { -return s->len + len < s->size; +return s->len + len <= s->size; }/**
@@ -110,8 +110,11 @@ int seq_buf_bitmask(struct seq_buf *s, const unsigned long *maskp,WARN_ON (s->size ==0 );/* -* The last byte of the buffer is used to determine if we -* overflowed or not. +* Note, because bitmap_scnprintf() only returns the number of bytes +* written and not the number that would be written, we use the last +* byte of the buffer to let us know if we overflowed. There's a small +* chance that the bitmap could have fit exactly inside the buffer, but +* it's not that critical if that does happen. */ if (len >1 ) { ret =bitmap_scnprintf (s->buffer + s->len, len, maskp, nmaskbits);
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 11/15 tracing: Add seq_buf_get_buf() and seq_buf_commit() helper functions
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
Add two helper functions; seq_buf_get_buf() and seq_buf_commit() that are used by seq_buf_path(). This makes the code similar to theseq_file: seq_path() function, and will help to be able to consolidate the functions between seq_file and trace_seq.
--- include/linux/seq_buf.h | 41 +++++++++++++++++++++++++++++++++++++++++ kernel/trace/seq_buf.c | 7 +++---- 2 files changed, 44 insertions(+), 4 deletions(-)
include/linux/seq_buf.h +41 -0
@@ -70,6 +70,47 @@ static inline unsigned int seq_buf_used(struct seq_buf *s)return min (s->len, s->size); } +/** +* seq_buf_get_buf - get buffer to write arbitrary data to +* @s: the seq_buf handle +* @bufp: the beginning of the buffer is stored here +* +* Return the number of bytes available in the buffer, or zero if +* there's no space. +*/ +static inline size_t seq_buf_get_buf (struct seq_buf *s,char **bufp) +{ +WARN_ON (s->len > s->size +1 ); + +if (s->len < s->size) { + *bufp = s->buffer + s->len; +return s->size - s->len; + } + + *bufp =NULL ; +return 0 ; +} + +/** +* seq_buf_commit - commit data to the buffer +* @s: the seq_buf handle +* @num: the number of bytes to commit +* +* Commit @num bytes of data written to a buffer previously acquired +* by seq_buf_get. To signal an error condition, or that the data +* didn't fit in the available space, pass a negative @num value. +*/ +static inline void seq_buf_commit (struct seq_buf *s,int num) +{ +if (num <0 ) { +seq_buf_set_overflow (s); + }else { +/* num must be negative on overflow */ +BUG_ON (s->len + num > s->size); + s->len += num; + } +} + extern __printf(2, 3) int seq_buf_printf(struct seq_buf *s, const char *fmt, ...); extern __printf(2, 0)
kernel/trace/seq_buf.c +3 -4
@@ -293,8 +293,8 @@ int seq_buf_putmem_hex(struct seq_buf *s, const void *mem,*/ int seq_buf_path (struct seq_buf *s,const struct path *path,const char *esc) { -char *buf = s->buffer + s->len; -size_t size =seq_buf_buffer_left (s); +char *buf; +size_t size =seq_buf_get_buf (s, &buf);int res =-1 ;WARN_ON (s->size ==0 );
@@ -307,8 +307,7 @@ int seq_buf_path(struct seq_buf *s, const struct path *path, const char *esc) res = end - buf; } } -if (res >0 ) - s->len += res; +seq_buf_commit (s, res);return res; }
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 12/15 seq-buf: Make seq_buf_bprintf() conditional on CONFIG_BINARY_PRINTF
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
The function bstr_printf() from lib/vsprnintf.c is only available if CONFIG_BINARY_PRINTF is defined. This is due to the only user currently being the tracing infrastructure, which needs to select this config when tracing is configured. Until there is another user of the binary printf formats, this will continue to be the case.
Since seq_buf.c is now lives in lib/ and is compiled even without tracing, it must encompass its use of bstr_printf() which is used by seq_buf_printf(). This too is only used by the tracing infrastructure and is still encapsulated by the CONFIG_BINARY_PRINTF.
--- include/linux/seq_buf.h | 7 +++++-- kernel/trace/seq_buf.c | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-)
include/linux/seq_buf.h +5 -2
@@ -115,8 +115,6 @@ extern __printf(2, 3)int seq_buf_printf (struct seq_buf *s,const char *fmt, ...); extern __printf(2, 0) int seq_buf_vprintf(struct seq_buf *s, const char *fmt, va_list args); -extern int -seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary); extern int seq_buf_print_seq(struct seq_file *m, struct seq_buf *s); extern int seq_buf_to_user(struct seq_buf *s, char __user *ubuf, int cnt);
@@ -130,4 +128,9 @@ extern int seq_buf_path(struct seq_buf *s, const struct path *path, const char *extern int seq_buf_bitmask (struct seq_buf *s,const unsigned long *maskp,int nmaskbits); +#ifdef CONFIG_BINARY_PRINTF +extern int +seq_buf_bprintf (struct seq_buf *s,const char *fmt,const u32 *binary); +#endif +#endif /* _LINUX_SEQ_BUF_H */
kernel/trace/seq_buf.c +2 -0
@@ -127,6 +127,7 @@ int seq_buf_bitmask(struct seq_buf *s, const unsigned long *maskp,return -1 ; } +#ifdef CONFIG_BINARY_PRINTF /** * seq_buf_bprintf - Write the printf string from binary arguments * @s: seq_buf descriptor
@@ -161,6 +162,7 @@ int seq_buf_bprintf(struct seq_buf *s, const char *fmt, const u32 *binary)seq_buf_set_overflow (s); return -1; } +#endif /* CONFIG_BINARY_PRINTF */ /** * seq_buf_puts - sequence printing of simple string
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 13/15 seq_buf: Move the seq_buf code to lib/
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
The seq_buf functions are rather useful outside of tracing. Instead of having it be dependent on CONFIG_TRACING, move the code into lib/ and allow other users to have access to it even when tracing is not configured.
The seq_buf utility is similar to the seq_file utility, but instead of writing sending data back up to userland, it writes it into a buffer defined at seq_buf_init(). This allows us to send a descriptor around that writes printf() formatted strings into it that can be retrieved later.
It is currently used by the tracing facility for such things like trace events to convert its binary saved data in the ring buffer into an ASCII human readable context to be displayed in /sys/kernel/debug/trace.
It can also be used for doing NMI prints safely from NMI context into the seq_buf and retrieved later and dumped to printk() safely. Doing printk() from an NMI context is dangerous because an NMI can preempt a current printk() and deadlock on it.
--- kernel/trace/Makefile | 1 - kernel/trace/seq_buf.c | 359 ------------------------------------------------- lib/Makefile | 2 +- lib/seq_buf.c | 359 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 360 insertions(+), 361 deletions(-) delete mode 100644 kernel/trace/seq_buf.c create mode 100644 lib/seq_buf.c
kernel/trace/Makefile +0 -1
@@ -29,7 +29,6 @@ obj-$(CONFIG_RING_BUFFER_BENCHMARK) += ring_buffer_benchmark.o obj-$(CONFIG_TRACING) += trace.o obj-$(CONFIG_TRACING) += trace_output.o obj-$(CONFIG_TRACING) += trace_seq.o -obj-$(CONFIG_TRACING) += seq_buf.o obj-$(CONFIG_TRACING) += trace_stat.o obj-$(CONFIG_TRACING) += trace_printk.o obj-$(CONFIG_CONTEXT_SWITCH_TRACER) += trace_sched_switch.o
kernel/trace/seq_buf.c deleted +0 -359
359 lines not on this page · raw message
lib/Makefile +1 -1
@@ -13,7 +13,7 @@ lib-y := ctype.o string.o vsprintf.o cmdline.o \ sha1.o md5.o irq_regs.o reciprocal_div.o argv_split.o \ proportions.o flex_proportions.o ratelimit.o show_mem.o \ is_single_threaded.o plist.o decompress.o kobject_uevent.o \ - earlycpio.o + earlycpio.o seq_buf.o obj-$(CONFIG_ARCH_HAS_DEBUG_STRICT_USER_COPY_CHECKS) += usercopy.o lib-$(CONFIG_MMU) += ioremap.o
lib/seq_buf.c new file +359 -0
359 lines not on this page · raw message
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 14/15 printk: Add per_cpu printk func to allow printk to be diverted
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
Being able to divert printk to call another function besides the normal logging is useful for such things like NMI handling. If some functions are to be called from NMI that does printk() it is possible to lock up the box if the nmi handler triggers when another printk is happening.
One example of this use is to perform a stack trace on all CPUs via NMI. But if the NMI is to do the printk() it can cause the system to lock up. By allowing the printk to be diverted to another function that can safely record the printk output and then print it when it in a safe context then NMIs will be safe to call these functions like show_regs().
--- include/linux/percpu.h | 3 +++ include/linux/printk.h | 2 ++ kernel/printk/printk.c | 38 +++++++++++++++++++++++++++++--------- 3 files changed, 34 insertions(+), 9 deletions(-)
include/linux/percpu.h +3 -0
@@ -134,4 +134,7 @@ extern phys_addr_t per_cpu_ptr_to_phys(void *addr); (typeof(type) __percpu *)__alloc_percpu(sizeof(type), \ __alignof__(type )) +/* To avoid include hell, as printk can not declare this, we declare it here */ +DECLARE_PER_CPU (printk_func_t, printk_func); +#endif /* __LINUX_PERCPU_H */
include/linux/printk.h +2 -0
@@ -162,6 +162,8 @@ extern int kptr_restrict;extern void wake_up_klogd (void ); +typedef int (*printk_func_t )(const char *fmt,va_list args); +void log_buf_kexec_setup (void );void __init setup_log_buf (int early);void dump_stack_set_arch_desc (const char *fmt, ...);
kernel/printk/printk.c +29 -9
@@ -1807,6 +1807,30 @@ asmlinkage int printk_emit(int facility, int level, }EXPORT_SYMBOL (printk_emit); +int vprintk_default (const char *fmt,va_list args) +{ +int r; + +#ifdef CONFIG_KGDB_KDB +if (unlikely (kdb_trap_printk)) { + r =vkdb_printf (fmt, args); +return r; + } +#endif + r =vprintk_emit (0 ,-1 ,NULL ,0 , fmt, args); + +return r; +} +EXPORT_SYMBOL_GPL (vprintk_default); + +/* +* This allows printk to be diverted to another function per cpu. +* This is useful for calling printk functions from within NMI +* without worrying about race conditions that can lock up the +* box. +*/ +DEFINE_PER_CPU (printk_func_t, printk_func) = vprintk_default; +/** * printk - print a kernel message * @fmt: format string
@@ -1830,19 +1854,15 @@ EXPORT_SYMBOL(printk_emit);*/ asmlinkage __visible int printk(const char *fmt, ...) { +printk_func_t vprintk_func;va_list args;int r; -#ifdef CONFIG_KGDB_KDB -if (unlikely (kdb_trap_printk)) { -va_start (args, fmt); - r =vkdb_printf (fmt, args); -va_end (args); -return r; - } -#endif va_start (args, fmt); - r =vprintk_emit (0 ,-1 ,NULL ,0 , fmt, args); +preempt_disable (); + vprintk_func =this_cpu_read (printk_func); + r =vprintk_func (fmt, args); +preempt_enable ();va_end (args);return r;
Signature · 1 line
2.1.1
Steven Rostedt
maintainerPATCH 15/15 x86/nmi: Perform a safe NMI stack trace on all CPUs
In reply to Steven RostedtFrom: "Steven Rostedt (Red Hat)" <[email protected]>
When trigger_all_cpu_backtrace() is called on x86, it will trigger an NMI on each CPU and call show_regs(). But this can lead to a hard lock up if the NMI comes in on another printk().
In order to avoid this, when the NMI triggers, it switches the printk routine for that CPU to call a NMI safe printk function that records the printk in a per_cpu seq_buf descriptor. After all NMIs have finished recording its data, the seq_bufs are printed in a safe context.
--- arch/x86/kernel/apic/hw_nmi.c | 91 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 5 deletions(-)
arch/x86/kernel/apic/hw_nmi.c +86 -5
@@ -18,6 +18,7 @@#include <linux/nmi.h> #include <linux/module.h> #include <linux/delay.h> +#include <linux/seq_buf.h> #ifdef CONFIG_HARDLOCKUP_DETECTOR u64 hw_nmi_get_sample_period (int watchdog_thresh)
@@ -29,14 +30,35 @@ u64 hw_nmi_get_sample_period(int watchdog_thresh)#ifdef arch_trigger_all_cpu_backtrace/* For reliability, we're prepared to waste bits here. */ static DECLARE_BITMAP(backtrace_mask, NR_CPUS) __read_mostly; +static cpumask_var_t printtrace_mask; + +#define NMI_BUF_SIZE 4096 + +struct nmi_seq_buf { +unsigned char buffer[NMI_BUF_SIZE ]; +struct seq_buf seq; +}; + +/* Safe printing in NMI context */ +static DEFINE_PER_CPU(struct nmi_seq_buf, nmi_print_seq);/* "in progress" flag of arch_trigger_all_cpu_backtrace */ static unsigned long backtrace_flag; +static void print_seq_line (struct nmi_seq_buf *s,int start,int end) +{ +const char *buf = s->buffer + start; + +printk ("%.*s" , (end - start) +1 , buf); +} +void arch_trigger_all_cpu_backtrace (bool include_self) { +struct nmi_seq_buf *s; +int len; +int cpu;int i; -int cpu =get_cpu (); +int this_cpu =get_cpu ();if (test_and_set_bit (0 , &backtrace_flag)) {/*
@@ -49,7 +71,17 @@ void arch_trigger_all_cpu_backtrace(bool include_self)cpumask_copy (to_cpumask (backtrace_mask), cpu_online_mask);if (!include_self) -cpumask_clear_cpu (cpu,to_cpumask (backtrace_mask)); +cpumask_clear_cpu (this_cpu,to_cpumask (backtrace_mask)); + +cpumask_copy (printtrace_mask,to_cpumask (backtrace_mask)); +/* +* Set up per_cpu seq_buf buffers that the NMIs running on the other +* CPUs will write to. +*/ +for_each_cpu (cpu,to_cpumask (backtrace_mask)) { + s = &per_cpu (nmi_print_seq, cpu); +seq_buf_init (&s->seq, s->buffer,NMI_BUF_SIZE ); + }if (!cpumask_empty (to_cpumask (backtrace_mask))) { pr_info("sending NMI to %s CPUs:\n",
@@ -65,11 +97,58 @@ void arch_trigger_all_cpu_backtrace(bool include_self)touch_softlockup_watchdog (); } +/* +* Now that all the NMIs have triggered, we can dump out their +* back traces safely to the console. +*/ +for_each_cpu (cpu, printtrace_mask) { +int last_i =0 ; + + s = &per_cpu (nmi_print_seq, cpu); + len =seq_buf_used (&s->seq); +if (!len) +continue ; + +/* Print line by line. */ +for (i =0 ; i < len; i++) { +if (s->buffer[i] =='\n' ) { +print_seq_line (s, last_i, i); + last_i = i +1 ; + } + } +/* Check if there was a partial line. */ +if (last_i < len) { +print_seq_line (s, last_i, len -1 ); +pr_cont ("\n" ); + } + } +clear_bit (0 , &backtrace_flag);smp_mb__after_atomic ();put_cpu (); } +/* +* It is not safe to call printk() directly from NMI handlers. +* It may be fine if the NMI detected a lock up and we have no choice +* but to do so, but doing a NMI on all other CPUs to get a back trace +* can be done with a sysrq-l. We don't want that to lock up, which +* can happen if the NMI interrupts a printk in progress. +* +* Instead, we redirect the vprintk() to this nmi_vprintk() that writes +* the content into a per cpu seq_buf buffer. Then when the NMIs are +* all done, we can safely dump the contents of the seq_buf to a printk() +* from a non NMI context. +*/ +static int nmi_vprintk (const char *fmt,va_list args) +{ +struct nmi_seq_buf *s =this_cpu_ptr (&nmi_print_seq); +unsigned int len =seq_buf_used (&s->seq); + +seq_buf_vprintf (&s->seq, fmt, args); +return seq_buf_used (&s->seq) - len; +} +static int arch_trigger_all_cpu_backtrace_handler (unsigned int cmd,struct pt_regs *regs) {
@@ -78,12 +157,14 @@ arch_trigger_all_cpu_backtrace_handler(unsigned int cmd, struct pt_regs *regs) cpu =smp_processor_id ();if (cpumask_test_cpu (cpu,to_cpumask (backtrace_mask))) { -static arch_spinlock_t lock = __ARCH_SPIN_LOCK_UNLOCKED; +printk_func_t printk_func_save =this_cpu_read (printk_func); -arch_spin_lock (&lock); +/* Replace printk to write into the NMI seq */ +this_cpu_write (printk_func, nmi_vprintk);printk (KERN_WARNING "NMI backtrace for cpu %d\n" , cpu);show_regs (regs); -arch_spin_unlock (&lock); +this_cpu_write (printk_func, printk_func_save); +cpumask_clear_cpu (cpu,to_cpumask (backtrace_mask));return NMI_HANDLED ; }
Signature · 1 line
2.1.1