aboutsummaryrefslogtreecommitdiffstats
path: root/arch/mips/kernel/process.c
diff options
context:
space:
mode:
authorPaul Burton <paul.burton@imgtec.com>2016-11-07 10:07:06 -0500
committerRalf Baechle <ralf@linux-mips.org>2017-01-03 10:34:37 -0500
commitbb9bc4689b9c635714fbcd5d335bad9934a7ebfc (patch)
treeed8b708c83cb73604a8b04a6095dee768be7ccdb /arch/mips/kernel/process.c
parent67c75057709a6d85c681c78b9b2f9b71191f01a2 (diff)
MIPS: Calculate microMIPS ra properly when unwinding the stack
get_frame_info() calculates the offset of the return address within a stack frame simply by dividing a the bottom 16 bits of the instruction, treated as a signed integer, by the size of a long. Whilst this works for MIPS32 & MIPS64 ISAs where the sw or sd instructions are used, it's incorrect for microMIPS where encodings differ. The result is that we typically completely fail to unwind the stack on microMIPS. Fix this by adjusting is_ra_save_ins() to calculate the return address offset, and take into account the various different encodings there in the same place as we consider whether an instruction is storing the ra/$31 register. With this we are now able to unwind the stack for kernels targetting the microMIPS ISA, for example we can produce: Call Trace: [<80109e1f>] show_stack+0x63/0x7c [<8011ea17>] __warn+0x9b/0xac [<8011ea45>] warn_slowpath_fmt+0x1d/0x20 [<8013fe53>] register_console+0x43/0x314 [<8067c58d>] of_setup_earlycon+0x1dd/0x1ec [<8067f63f>] early_init_dt_scan_chosen_stdout+0xe7/0xf8 [<8066c115>] do_early_param+0x75/0xac [<801302f9>] parse_args+0x1dd/0x308 [<8066c459>] parse_early_options+0x25/0x28 [<8066c48b>] parse_early_param+0x2f/0x38 [<8066e8cf>] setup_arch+0x113/0x488 [<8066c4f3>] start_kernel+0x57/0x328 ---[ end trace 0000000000000000 ]--- Whereas previously we only produced: Call Trace: [<80109e1f>] show_stack+0x63/0x7c ---[ end trace 0000000000000000 ]--- Signed-off-by: Paul Burton <paul.burton@imgtec.com> Fixes: 34c2f668d0f6 ("MIPS: microMIPS: Add unaligned access support.") Cc: Leonid Yegoshin <leonid.yegoshin@imgtec.com> Cc: linux-mips@linux-mips.org Cc: <stable@vger.kernel.org> # v3.10+ Patchwork: https://patchwork.linux-mips.org/patch/14532/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/kernel/process.c')
-rw-r--r--arch/mips/kernel/process.c83
1 files changed, 63 insertions, 20 deletions
diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c
index e03113493580..801b399d4861 100644
--- a/arch/mips/kernel/process.c
+++ b/arch/mips/kernel/process.c
@@ -196,7 +196,7 @@ struct mips_frame_info {
196#define J_TARGET(pc,target) \ 196#define J_TARGET(pc,target) \
197 (((unsigned long)(pc) & 0xf0000000) | ((target) << 2)) 197 (((unsigned long)(pc) & 0xf0000000) | ((target) << 2))
198 198
199static inline int is_ra_save_ins(union mips_instruction *ip) 199static inline int is_ra_save_ins(union mips_instruction *ip, int *poff)
200{ 200{
201#ifdef CONFIG_CPU_MICROMIPS 201#ifdef CONFIG_CPU_MICROMIPS
202 /* 202 /*
@@ -209,25 +209,70 @@ static inline int is_ra_save_ins(union mips_instruction *ip)
209 * microMIPS is way more fun... 209 * microMIPS is way more fun...
210 */ 210 */
211 if (mm_insn_16bit(ip->halfword[1])) { 211 if (mm_insn_16bit(ip->halfword[1])) {
212 return (ip->mm16_r5_format.opcode == mm_swsp16_op && 212 switch (ip->mm16_r5_format.opcode) {
213 ip->mm16_r5_format.rt == 31) || 213 case mm_swsp16_op:
214 (ip->mm16_m_format.opcode == mm_pool16c_op && 214 if (ip->mm16_r5_format.rt != 31)
215 ip->mm16_m_format.func == mm_swm16_op); 215 return 0;
216
217 *poff = ip->mm16_r5_format.simmediate;
218 *poff = (*poff << 2) / sizeof(ulong);
219 return 1;
220
221 case mm_pool16c_op:
222 switch (ip->mm16_m_format.func) {
223 case mm_swm16_op:
224 *poff = ip->mm16_m_format.imm;
225 *poff += 1 + ip->mm16_m_format.rlist;
226 *poff = (*poff << 2) / sizeof(ulong);
227 return 1;
228
229 default:
230 return 0;
231 }
232
233 default:
234 return 0;
235 }
216 } 236 }
217 else { 237
218 return (ip->mm_m_format.opcode == mm_pool32b_op && 238 switch (ip->i_format.opcode) {
219 ip->mm_m_format.rd > 9 && 239 case mm_sw32_op:
220 ip->mm_m_format.base == 29 && 240 if (ip->i_format.rs != 29)
221 ip->mm_m_format.func == mm_swm32_func) || 241 return 0;
222 (ip->i_format.opcode == mm_sw32_op && 242 if (ip->i_format.rt != 31)
223 ip->i_format.rs == 29 && 243 return 0;
224 ip->i_format.rt == 31); 244
245 *poff = ip->i_format.simmediate / sizeof(ulong);
246 return 1;
247
248 case mm_pool32b_op:
249 switch (ip->mm_m_format.func) {
250 case mm_swm32_func:
251 if (ip->mm_m_format.rd < 0x10)
252 return 0;
253 if (ip->mm_m_format.base != 29)
254 return 0;
255
256 *poff = ip->mm_m_format.simmediate;
257 *poff += (ip->mm_m_format.rd & 0xf) * sizeof(u32);
258 *poff /= sizeof(ulong);
259 return 1;
260 default:
261 return 0;
262 }
263
264 default:
265 return 0;
225 } 266 }
226#else 267#else
227 /* sw / sd $ra, offset($sp) */ 268 /* sw / sd $ra, offset($sp) */
228 return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) && 269 if ((ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) &&
229 ip->i_format.rs == 29 && 270 ip->i_format.rs == 29 && ip->i_format.rt == 31) {
230 ip->i_format.rt == 31; 271 *poff = ip->i_format.simmediate / sizeof(ulong);
272 return 1;
273 }
274
275 return 0;
231#endif 276#endif
232} 277}
233 278
@@ -350,11 +395,9 @@ static int get_frame_info(struct mips_frame_info *info)
350 } 395 }
351 continue; 396 continue;
352 } 397 }
353 if (info->pc_offset == -1 && is_ra_save_ins(&insn)) { 398 if (info->pc_offset == -1 &&
354 info->pc_offset = 399 is_ra_save_ins(&insn, &info->pc_offset))
355 ip->i_format.simmediate / sizeof(long);
356 break; 400 break;
357 }
358 } 401 }
359 if (info->frame_size && info->pc_offset >= 0) /* nested */ 402 if (info->frame_size && info->pc_offset >= 0) /* nested */
360 return 0; 403 return 0;