diff options
author | Dave Martin <Dave.Martin@arm.com> | 2017-10-31 11:50:53 -0400 |
---|---|---|
committer | Will Deacon <will.deacon@arm.com> | 2017-11-03 11:24:11 -0400 |
commit | 27e64b4be4b863d884f3ec1686a2f744ae93a1b9 (patch) | |
tree | ae548b3f473891f3aec3b0cbc53d44173fb101e7 | |
parent | c7f5828bf77dcbd61d51f4736c1d5aa35663fbb4 (diff) |
regset: Add support for dynamically sized regsets
Currently the regset API doesn't allow for the possibility that
regsets (or at least, the amount of meaningful data in a regset)
may change in size.
In particular, this results in useless padding being added to
coredumps if a regset's current size is smaller than its
theoretical maximum size.
This patch adds a get_size() function to struct user_regset.
Individual regset implementations can implement this function to
return the current size of the regset data. A regset_size()
function is added to provide callers with an abstract interface for
determining the size of a regset without needing to know whether
the regset is dynamically sized or not.
The only affected user of this interface is the ELF coredump code:
This patch ports ELF coredump to dump regsets with their actual
size in the coredump. This has no effect except for new regsets
that are dynamically sized and provide a get_size() implementation.
Signed-off-by: Dave Martin <Dave.Martin@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Dmitry Safonov <dsafonov@virtuozzo.com>
Cc: H. J. Lu <hjl.tools@gmail.com>
Signed-off-by: Will Deacon <will.deacon@arm.com>
-rw-r--r-- | fs/binfmt_elf.c | 10 | ||||
-rw-r--r-- | include/linux/regset.h | 67 |
2 files changed, 65 insertions, 12 deletions
diff --git a/fs/binfmt_elf.c b/fs/binfmt_elf.c index 73b01e474fdc..c697882b3aba 100644 --- a/fs/binfmt_elf.c +++ b/fs/binfmt_elf.c | |||
@@ -1699,7 +1699,7 @@ static int fill_thread_core_info(struct elf_thread_core_info *t, | |||
1699 | long signr, size_t *total) | 1699 | long signr, size_t *total) |
1700 | { | 1700 | { |
1701 | unsigned int i; | 1701 | unsigned int i; |
1702 | unsigned int regset_size = view->regsets[0].n * view->regsets[0].size; | 1702 | unsigned int regset0_size = regset_size(t->task, &view->regsets[0]); |
1703 | 1703 | ||
1704 | /* | 1704 | /* |
1705 | * NT_PRSTATUS is the one special case, because the regset data | 1705 | * NT_PRSTATUS is the one special case, because the regset data |
@@ -1708,11 +1708,11 @@ static int fill_thread_core_info(struct elf_thread_core_info *t, | |||
1708 | * We assume that regset 0 is NT_PRSTATUS. | 1708 | * We assume that regset 0 is NT_PRSTATUS. |
1709 | */ | 1709 | */ |
1710 | fill_prstatus(&t->prstatus, t->task, signr); | 1710 | fill_prstatus(&t->prstatus, t->task, signr); |
1711 | (void) view->regsets[0].get(t->task, &view->regsets[0], 0, regset_size, | 1711 | (void) view->regsets[0].get(t->task, &view->regsets[0], 0, regset0_size, |
1712 | &t->prstatus.pr_reg, NULL); | 1712 | &t->prstatus.pr_reg, NULL); |
1713 | 1713 | ||
1714 | fill_note(&t->notes[0], "CORE", NT_PRSTATUS, | 1714 | fill_note(&t->notes[0], "CORE", NT_PRSTATUS, |
1715 | PRSTATUS_SIZE(t->prstatus, regset_size), &t->prstatus); | 1715 | PRSTATUS_SIZE(t->prstatus, regset0_size), &t->prstatus); |
1716 | *total += notesize(&t->notes[0]); | 1716 | *total += notesize(&t->notes[0]); |
1717 | 1717 | ||
1718 | do_thread_regset_writeback(t->task, &view->regsets[0]); | 1718 | do_thread_regset_writeback(t->task, &view->regsets[0]); |
@@ -1728,7 +1728,7 @@ static int fill_thread_core_info(struct elf_thread_core_info *t, | |||
1728 | if (regset->core_note_type && regset->get && | 1728 | if (regset->core_note_type && regset->get && |
1729 | (!regset->active || regset->active(t->task, regset))) { | 1729 | (!regset->active || regset->active(t->task, regset))) { |
1730 | int ret; | 1730 | int ret; |
1731 | size_t size = regset->n * regset->size; | 1731 | size_t size = regset_size(t->task, regset); |
1732 | void *data = kmalloc(size, GFP_KERNEL); | 1732 | void *data = kmalloc(size, GFP_KERNEL); |
1733 | if (unlikely(!data)) | 1733 | if (unlikely(!data)) |
1734 | return 0; | 1734 | return 0; |
@@ -1743,7 +1743,7 @@ static int fill_thread_core_info(struct elf_thread_core_info *t, | |||
1743 | size, data); | 1743 | size, data); |
1744 | else { | 1744 | else { |
1745 | SET_PR_FPVALID(&t->prstatus, | 1745 | SET_PR_FPVALID(&t->prstatus, |
1746 | 1, regset_size); | 1746 | 1, regset0_size); |
1747 | fill_note(&t->notes[i], "CORE", | 1747 | fill_note(&t->notes[i], "CORE", |
1748 | NT_PRFPREG, size, data); | 1748 | NT_PRFPREG, size, data); |
1749 | } | 1749 | } |
diff --git a/include/linux/regset.h b/include/linux/regset.h index 8e0c9febf495..494cedaafdf2 100644 --- a/include/linux/regset.h +++ b/include/linux/regset.h | |||
@@ -107,6 +107,28 @@ typedef int user_regset_writeback_fn(struct task_struct *target, | |||
107 | int immediate); | 107 | int immediate); |
108 | 108 | ||
109 | /** | 109 | /** |
110 | * user_regset_get_size_fn - type of @get_size function in &struct user_regset | ||
111 | * @target: thread being examined | ||
112 | * @regset: regset being examined | ||
113 | * | ||
114 | * This call is optional; usually the pointer is %NULL. | ||
115 | * | ||
116 | * When provided, this function must return the current size of regset | ||
117 | * data, as observed by the @get function in &struct user_regset. The | ||
118 | * value returned must be a multiple of @size. The returned size is | ||
119 | * required to be valid only until the next time (if any) @regset is | ||
120 | * modified for @target. | ||
121 | * | ||
122 | * This function is intended for dynamically sized regsets. A regset | ||
123 | * that is statically sized does not need to implement it. | ||
124 | * | ||
125 | * This function should not be called directly: instead, callers should | ||
126 | * call regset_size() to determine the current size of a regset. | ||
127 | */ | ||
128 | typedef unsigned int user_regset_get_size_fn(struct task_struct *target, | ||
129 | const struct user_regset *regset); | ||
130 | |||
131 | /** | ||
110 | * struct user_regset - accessible thread CPU state | 132 | * struct user_regset - accessible thread CPU state |
111 | * @n: Number of slots (registers). | 133 | * @n: Number of slots (registers). |
112 | * @size: Size in bytes of a slot (register). | 134 | * @size: Size in bytes of a slot (register). |
@@ -117,19 +139,33 @@ typedef int user_regset_writeback_fn(struct task_struct *target, | |||
117 | * @set: Function to store values. | 139 | * @set: Function to store values. |
118 | * @active: Function to report if regset is active, or %NULL. | 140 | * @active: Function to report if regset is active, or %NULL. |
119 | * @writeback: Function to write data back to user memory, or %NULL. | 141 | * @writeback: Function to write data back to user memory, or %NULL. |
142 | * @get_size: Function to return the regset's size, or %NULL. | ||
120 | * | 143 | * |
121 | * This data structure describes a machine resource we call a register set. | 144 | * This data structure describes a machine resource we call a register set. |
122 | * This is part of the state of an individual thread, not necessarily | 145 | * This is part of the state of an individual thread, not necessarily |
123 | * actual CPU registers per se. A register set consists of a number of | 146 | * actual CPU registers per se. A register set consists of a number of |
124 | * similar slots, given by @n. Each slot is @size bytes, and aligned to | 147 | * similar slots, given by @n. Each slot is @size bytes, and aligned to |
125 | * @align bytes (which is at least @size). | 148 | * @align bytes (which is at least @size). For dynamically-sized |
149 | * regsets, @n must contain the maximum possible number of slots for the | ||
150 | * regset, and @get_size must point to a function that returns the | ||
151 | * current regset size. | ||
126 | * | 152 | * |
127 | * These functions must be called only on the current thread or on a | 153 | * Callers that need to know only the current size of the regset and do |
128 | * thread that is in %TASK_STOPPED or %TASK_TRACED state, that we are | 154 | * not care about its internal structure should call regset_size() |
129 | * guaranteed will not be woken up and return to user mode, and that we | 155 | * instead of inspecting @n or calling @get_size. |
130 | * have called wait_task_inactive() on. (The target thread always might | 156 | * |
131 | * wake up for SIGKILL while these functions are working, in which case | 157 | * For backward compatibility, the @get and @set methods must pad to, or |
132 | * that thread's user_regset state might be scrambled.) | 158 | * accept, @n * @size bytes, even if the current regset size is smaller. |
159 | * The precise semantics of these operations depend on the regset being | ||
160 | * accessed. | ||
161 | * | ||
162 | * The functions to which &struct user_regset members point must be | ||
163 | * called only on the current thread or on a thread that is in | ||
164 | * %TASK_STOPPED or %TASK_TRACED state, that we are guaranteed will not | ||
165 | * be woken up and return to user mode, and that we have called | ||
166 | * wait_task_inactive() on. (The target thread always might wake up for | ||
167 | * SIGKILL while these functions are working, in which case that | ||
168 | * thread's user_regset state might be scrambled.) | ||
133 | * | 169 | * |
134 | * The @pos argument must be aligned according to @align; the @count | 170 | * The @pos argument must be aligned according to @align; the @count |
135 | * argument must be a multiple of @size. These functions are not | 171 | * argument must be a multiple of @size. These functions are not |
@@ -156,6 +192,7 @@ struct user_regset { | |||
156 | user_regset_set_fn *set; | 192 | user_regset_set_fn *set; |
157 | user_regset_active_fn *active; | 193 | user_regset_active_fn *active; |
158 | user_regset_writeback_fn *writeback; | 194 | user_regset_writeback_fn *writeback; |
195 | user_regset_get_size_fn *get_size; | ||
159 | unsigned int n; | 196 | unsigned int n; |
160 | unsigned int size; | 197 | unsigned int size; |
161 | unsigned int align; | 198 | unsigned int align; |
@@ -371,5 +408,21 @@ static inline int copy_regset_from_user(struct task_struct *target, | |||
371 | return regset->set(target, regset, offset, size, NULL, data); | 408 | return regset->set(target, regset, offset, size, NULL, data); |
372 | } | 409 | } |
373 | 410 | ||
411 | /** | ||
412 | * regset_size - determine the current size of a regset | ||
413 | * @target: thread to be examined | ||
414 | * @regset: regset to be examined | ||
415 | * | ||
416 | * Note that the returned size is valid only until the next time | ||
417 | * (if any) @regset is modified for @target. | ||
418 | */ | ||
419 | static inline unsigned int regset_size(struct task_struct *target, | ||
420 | const struct user_regset *regset) | ||
421 | { | ||
422 | if (!regset->get_size) | ||
423 | return regset->n * regset->size; | ||
424 | else | ||
425 | return regset->get_size(target, regset); | ||
426 | } | ||
374 | 427 | ||
375 | #endif /* <linux/regset.h> */ | 428 | #endif /* <linux/regset.h> */ |