aboutsummaryrefslogtreecommitdiffstats
path: root/arch/mips/kernel
diff options
context:
space:
mode:
authorPaul Burton <paul.burton@imgtec.com>2016-11-07 10:07:06 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2017-03-12 00:41:40 -0500
commit209ad1941daafab4255990c3b885cf184a05d72e (patch)
tree76df30822bca77c4c825cc9765799998d9701765 /arch/mips/kernel
parentb14e085086245a829a87f6cc44c996333c641390 (diff)
MIPS: Calculate microMIPS ra properly when unwinding the stack
commit bb9bc4689b9c635714fbcd5d335bad9934a7ebfc upstream. 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 Patchwork: https://patchwork.linux-mips.org/patch/14532/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'arch/mips/kernel')
-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 298dc33737b0..39c946fce939 100644
--- a/arch/mips/kernel/process.c
+++ b/arch/mips/kernel/process.c
@@ -195,7 +195,7 @@ struct mips_frame_info {
195#define J_TARGET(pc,target) \ 195#define J_TARGET(pc,target) \
196 (((unsigned long)(pc) & 0xf0000000) | ((target) << 2)) 196 (((unsigned long)(pc) & 0xf0000000) | ((target) << 2))
197 197
198static inline int is_ra_save_ins(union mips_instruction *ip) 198static inline int is_ra_save_ins(union mips_instruction *ip, int *poff)
199{ 199{
200#ifdef CONFIG_CPU_MICROMIPS 200#ifdef CONFIG_CPU_MICROMIPS
201 /* 201 /*
@@ -208,25 +208,70 @@ static inline int is_ra_save_ins(union mips_instruction *ip)
208 * microMIPS is way more fun... 208 * microMIPS is way more fun...
209 */ 209 */
210 if (mm_insn_16bit(ip->halfword[1])) { 210 if (mm_insn_16bit(ip->halfword[1])) {
211 return (ip->mm16_r5_format.opcode == mm_swsp16_op && 211 switch (ip->mm16_r5_format.opcode) {
212 ip->mm16_r5_format.rt == 31) || 212 case mm_swsp16_op:
213 (ip->mm16_m_format.opcode == mm_pool16c_op && 213 if (ip->mm16_r5_format.rt != 31)
214 ip->mm16_m_format.func == mm_swm16_op); 214 return 0;
215
216 *poff = ip->mm16_r5_format.simmediate;
217 *poff = (*poff << 2) / sizeof(ulong);
218 return 1;
219
220 case mm_pool16c_op:
221 switch (ip->mm16_m_format.func) {
222 case mm_swm16_op:
223 *poff = ip->mm16_m_format.imm;
224 *poff += 1 + ip->mm16_m_format.rlist;
225 *poff = (*poff << 2) / sizeof(ulong);
226 return 1;
227
228 default:
229 return 0;
230 }
231
232 default:
233 return 0;
234 }
215 } 235 }
216 else { 236
217 return (ip->mm_m_format.opcode == mm_pool32b_op && 237 switch (ip->i_format.opcode) {
218 ip->mm_m_format.rd > 9 && 238 case mm_sw32_op:
219 ip->mm_m_format.base == 29 && 239 if (ip->i_format.rs != 29)
220 ip->mm_m_format.func == mm_swm32_func) || 240 return 0;
221 (ip->i_format.opcode == mm_sw32_op && 241 if (ip->i_format.rt != 31)
222 ip->i_format.rs == 29 && 242 return 0;
223 ip->i_format.rt == 31); 243
244 *poff = ip->i_format.simmediate / sizeof(ulong);
245 return 1;
246
247 case mm_pool32b_op:
248 switch (ip->mm_m_format.func) {
249 case mm_swm32_func:
250 if (ip->mm_m_format.rd < 0x10)
251 return 0;
252 if (ip->mm_m_format.base != 29)
253 return 0;
254
255 *poff = ip->mm_m_format.simmediate;
256 *poff += (ip->mm_m_format.rd & 0xf) * sizeof(u32);
257 *poff /= sizeof(ulong);
258 return 1;
259 default:
260 return 0;
261 }
262
263 default:
264 return 0;
224 } 265 }
225#else 266#else
226 /* sw / sd $ra, offset($sp) */ 267 /* sw / sd $ra, offset($sp) */
227 return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) && 268 if ((ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) &&
228 ip->i_format.rs == 29 && 269 ip->i_format.rs == 29 && ip->i_format.rt == 31) {
229 ip->i_format.rt == 31; 270 *poff = ip->i_format.simmediate / sizeof(ulong);
271 return 1;
272 }
273
274 return 0;
230#endif 275#endif
231} 276}
232 277
@@ -349,11 +394,9 @@ static int get_frame_info(struct mips_frame_info *info)
349 } 394 }
350 continue; 395 continue;
351 } 396 }
352 if (info->pc_offset == -1 && is_ra_save_ins(&insn)) { 397 if (info->pc_offset == -1 &&
353 info->pc_offset = 398 is_ra_save_ins(&insn, &info->pc_offset))
354 ip->i_format.simmediate / sizeof(long);
355 break; 399 break;
356 }
357 } 400 }
358 if (info->frame_size && info->pc_offset >= 0) /* nested */ 401 if (info->frame_size && info->pc_offset >= 0) /* nested */
359 return 0; 402 return 0;