diff mbox

[RFC] ARM: Add generic instruction opcode manipulation helpers

Message ID CAH+eYFBM36VqYAqZdiZnMP7AODh_SqYQWuoFDSyS3BDZDvHU5A@mail.gmail.com
State New
Headers show

Commit Message

Rabin Vincent Nov. 28, 2011, 4:59 p.m. UTC
On Fri, Nov 25, 2011 at 16:58, Dave Martin <dave.martin@linaro.org> wrote:
> +#ifdef CONFIG_CPU_ENDIAN_BE8
> +#define __opcode_to_mem_arm(x) swab32(x)
> +#define __opcode_to_mem_thumb16(x) swab16(x)
> +#define __opcode_to_mem_thumb32(x) swahb32(x)
> +#else
> +#define __opcode_to_mem_arm(x) (x) ((u32)(x))
> +#define __opcode_to_mem_thumb16(x) ((u16)(x))
> +#define __opcode_to_mem_thumb32(x) swahw32(x)
> +#endif

The current kprobes code does:

#ifndef __ARMEB__ /* Swap halfwords for little-endian */
                bkp = (bkp >> 16) | (bkp << 16);
#endif

There seems to be a difference between your macros and that for the case
__ARMEB__ + !CONFIG_CPU_ENDIAN_BE8.  Or is that combination not
possible?

> +
> +#define __mem_to_opcode_arm(x) __opcode_to_mem_arm(x)
> +#define __mem_to_opcode_thumb16(x) __opcode_to_mem_thumb16(x)
> +#define __mem_to_opcode_thumb32(x) __opcode_to_mem_thumb32(x)
> +
> +/* Operations specific to Thumb opcodes */
> +
> +/* Instruction size checks: */
> +#define __opcode_is_thumb32(x) ((u32)(x) >= 0xE8000000UL)
> +#define __opcode_is_thumb16(x) ((u32)(x) < 0xE800UL)
> +
> +/* Operations to construct or split 32-bit Thumb instructions: */
> +#define __opcode_thumb32_first(x) ((u16)((thumb_opcode) >> 16))
> +#define __opcode_thumb32_second(x) ((u16)(thumb_opcode))
> +#define __opcode_thumb32_compose(first, second) \
> +       (((u32)(u16)(first) << 16) | (u32)(u16)(second))

All of these could certainly find use in the files being touched by
the jump label patchset:

Comments

Dave Martin Nov. 28, 2011, 5:41 p.m. UTC | #1
On Mon, Nov 28, 2011 at 10:29:14PM +0530, Rabin Vincent wrote:
> On Fri, Nov 25, 2011 at 16:58, Dave Martin <dave.martin@linaro.org> wrote:
> > +#ifdef CONFIG_CPU_ENDIAN_BE8
> > +#define __opcode_to_mem_arm(x) swab32(x)
> > +#define __opcode_to_mem_thumb16(x) swab16(x)
> > +#define __opcode_to_mem_thumb32(x) swahb32(x)
> > +#else
> > +#define __opcode_to_mem_arm(x) (x) ((u32)(x))
> > +#define __opcode_to_mem_thumb16(x) ((u16)(x))
> > +#define __opcode_to_mem_thumb32(x) swahw32(x)
> > +#endif
> 
> The current kprobes code does:
> 
> #ifndef __ARMEB__ /* Swap halfwords for little-endian */
>                 bkp = (bkp >> 16) | (bkp << 16);
> #endif
> 
> There seems to be a difference between your macros and that for the case
> __ARMEB__ + !CONFIG_CPU_ENDIAN_BE8.  Or is that combination not
> possible?
> 

For building the kernel, it is effectively impossible, since you can't
have Thumb-2 code on BE32 platforms.  The opcode_to_mem_thumb*()
definitions are currently "don't cares" for this configuration in
my RFC, but we should probably clarify how things should behave in this
case.

The kprobes code does not look correct for the big-endian case, since
the bytes are not swapped -- this will result in big-endian words or
halfwords in memory, which would be correct for BE32 but not for BE8
(where instructions are always little-endian).

So they're both wrong, in different ways if I've understood correctly.


I'm not exactly sure how we handle BE32, or whether we need to.  I
believe that for BE32 it would be correct to leave the canonical
instruction completely un-swapped, because instructions are natively
big-endian on those platforms.  Note that BE32 is only applicable to
older versions of the architecture, and so Thumb-2 is not applicable to
any BE32 platform, so the only situation where we would care is if
kprobes, ftrace or similar allows breakpoints or tracepoints to be set
in userspace Thumb code on these platforms.

I think that __ARMEB__ encompasses any big-endian target including BE8
and BE32, so we might need to be a bit careful about how we use it.


Rabin, did the __opcode_read stuff look useful for ftrace?  My idea
was to factor out the logic of how to read/write a whole instruction,
but my proposal may be overkill...


Tixy, do you have a view on these issues?

> > +
> > +#define __mem_to_opcode_arm(x) __opcode_to_mem_arm(x)
> > +#define __mem_to_opcode_thumb16(x) __opcode_to_mem_thumb16(x)
> > +#define __mem_to_opcode_thumb32(x) __opcode_to_mem_thumb32(x)
> > +
> > +/* Operations specific to Thumb opcodes */
> > +
> > +/* Instruction size checks: */
> > +#define __opcode_is_thumb32(x) ((u32)(x) >= 0xE8000000UL)
> > +#define __opcode_is_thumb16(x) ((u32)(x) < 0xE800UL)
> > +
> > +/* Operations to construct or split 32-bit Thumb instructions: */
> > +#define __opcode_thumb32_first(x) ((u16)((thumb_opcode) >> 16))
> > +#define __opcode_thumb32_second(x) ((u16)(thumb_opcode))
> > +#define __opcode_thumb32_compose(first, second) \
> > +       (((u32)(u16)(first) << 16) | (u32)(u16)(second))
> 
> All of these could certainly find use in the files being touched by
> the jump label patchset:

I haven't reviewed everything, but yes -- that looks like the kind
of usage I was trying to enable.

Cheers
---Dave

> 
> diff --git a/arch/arm/kernel/ftrace.c b/arch/arm/kernel/ftrace.c
> index c46d18b..c437a9d 100644
> --- a/arch/arm/kernel/ftrace.c
> +++ b/arch/arm/kernel/ftrace.c
> @@ -72,12 +72,13 @@ static int ftrace_modify_code(unsigned long pc,
> unsigned long old,
>  {
>  	unsigned long replaced;
> 
> -#ifndef __ARMEB__
>  	if (IS_ENABLED(CONFIG_THUMB2_KERNEL)) {
> -		old = (old >> 16) | (old << 16);
> -		new = (new >> 16) | (new << 16);
> +		old = __opcode_to_mem_thumb32(old);
> +		new = __opcode_to_mem_thumb32(new);
> +	} else {
> +		old = __opcode_to_mem_arm(old);
> +		new = __opcode_to_mem_arm(new);
>  	}
> -#endif
> 
>  	if (old) {
>  		if (probe_kernel_read(&replaced, (void *)pc, MCOUNT_INSN_SIZE))
> diff --git a/arch/arm/kernel/insn.c b/arch/arm/kernel/insn.c
> index f65b68c..9079997 100644
> --- a/arch/arm/kernel/insn.c
> +++ b/arch/arm/kernel/insn.c
> @@ -27,7 +27,7 @@ __arm_gen_branch_thumb2(unsigned long pc, unsigned
> long addr, bool link)
>  	if (link)
>  		second |= 1 << 14;
> 
> -	return (first << 16) | second;
> +	return __opcode_thumb32_compose(first, second);
>  }
> 
>  static unsigned long
> diff --git a/arch/arm/kernel/patch.c b/arch/arm/kernel/patch.c
> index 15df8a5..5398659 100644
> --- a/arch/arm/kernel/patch.c
> +++ b/arch/arm/kernel/patch.c
> @@ -17,21 +17,21 @@ void __kprobes __patch_text(void *addr, unsigned int insn)
>  	bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL);
>  	int size;
> 
> -	if (thumb2 && !is_wide_instruction(insn)) {
> -		*(u16 *)addr = insn;
> +	if (thumb2 && __opcode_is_thumb16(insn)) {
> +		*(u16 *)addr = __opcode_to_mem_thumb16(insn);
>  		size = sizeof(u16);
>  	} else if (thumb2 && ((uintptr_t)addr & 2)) {
>  		u16 *addrh = addr;
> 
> -		addrw[0] = insn >> 16;
> -		addrw[1] = insn & 0xffff;
> +		addrw[0] = __opcode_thumb32_first(insn);
> +		addrw[1] = __opcode_thumb32_second(insn);
> 
>  		size = sizeof(u32);
>  	} else {
> -#ifndef __ARMEB__
>  		if (thumb2)
> -			insn = (insn >> 16) | (insn << 16);
> -#endif
> +			insn = __opcode_to_mem_thumb32(insn);
> +		else
> +			insn = __opcode_to_mem_arm(insn);
> 
>  		*(u32 *)addr = insn;
>  		size = sizeof(u32);
> @@ -61,7 +61,7 @@ void __kprobes patch_text(void *addr, unsigned int insn)
>  		stop_machine(patch_text_stop_machine, &patch, cpu_online_mask);
>  	} else {
>  		bool straddles_word = IS_ENABLED(CONFIG_THUMB2_KERNEL)
> -				      && is_wide_instruction(insn)
> +				      && __opcode_is_thumb32(insn)
>  				      && ((uintptr_t)addr & 2);
> 
>  		if (straddles_word)
Tixy Nov. 28, 2011, 7:20 p.m. UTC | #2
On Mon, 2011-11-28 at 17:41 +0000, Dave Martin wrote:
> On Mon, Nov 28, 2011 at 10:29:14PM +0530, Rabin Vincent wrote:
> > On Fri, Nov 25, 2011 at 16:58, Dave Martin <dave.martin@linaro.org> wrote:
> > > +#ifdef CONFIG_CPU_ENDIAN_BE8
> > > +#define __opcode_to_mem_arm(x) swab32(x)
> > > +#define __opcode_to_mem_thumb16(x) swab16(x)
> > > +#define __opcode_to_mem_thumb32(x) swahb32(x)
> > > +#else
> > > +#define __opcode_to_mem_arm(x) (x) ((u32)(x))
> > > +#define __opcode_to_mem_thumb16(x) ((u16)(x))
> > > +#define __opcode_to_mem_thumb32(x) swahw32(x)
> > > +#endif
> > 
> > The current kprobes code does:
> > 
> > #ifndef __ARMEB__ /* Swap halfwords for little-endian */
> >                 bkp = (bkp >> 16) | (bkp << 16);
> > #endif
> > 
> > There seems to be a difference between your macros and that for the case
> > __ARMEB__ + !CONFIG_CPU_ENDIAN_BE8.  Or is that combination not
> > possible?
> > 
> 
> For building the kernel, it is effectively impossible, since you can't
> have Thumb-2 code on BE32 platforms.  The opcode_to_mem_thumb*()
> definitions are currently "don't cares" for this configuration in
> my RFC, but we should probably clarify how things should behave in this
> case.
> 
> The kprobes code does not look correct for the big-endian case, since
> the bytes are not swapped -- this will result in big-endian words or
> halfwords in memory, which would be correct for BE32 but not for BE8
> (where instructions are always little-endian).
> 
> So they're both wrong, in different ways if I've understood correctly.
> 
> 
> I'm not exactly sure how we handle BE32, or whether we need to.  I
> believe that for BE32 it would be correct to leave the canonical
> instruction completely un-swapped, because instructions are natively
> big-endian on those platforms.  Note that BE32 is only applicable to
> older versions of the architecture, and so Thumb-2 is not applicable to
> any BE32 platform, so the only situation where we would care is if
> kprobes, ftrace or similar allows breakpoints or tracepoints to be set
> in userspace Thumb code on these platforms.
> 
> I think that __ARMEB__ encompasses any big-endian target including BE8
> and BE32, so we might need to be a bit careful about how we use it.
> 
> 
> Rabin, did the __opcode_read stuff look useful for ftrace?  My idea
> was to factor out the logic of how to read/write a whole instruction,
> but my proposal may be overkill...
> 
> 
> Tixy, do you have a view on these issues?

I had to read the ARM ARM, I wasn't aware of BE8 :-)

My view is that the the current kprobes code just doesn't handle BE8.
Anywhere where it reads the original instruction, writes a breakpoint or
restores the instruction would need to swap the byte order. To do that,
the proposed mem_to_opcode/opcode_to_mem helpers would be useful.

However, the read/write a whole instruction functions do look a bit
overkill. Especially if the number of places using these is small, due
to factorisations like Rabin's __patch_text().
Dave Martin Nov. 29, 2011, 10:42 a.m. UTC | #3
On Mon, Nov 28, 2011 at 07:20:33PM +0000, Tixy wrote:
> On Mon, 2011-11-28 at 17:41 +0000, Dave Martin wrote:
> > On Mon, Nov 28, 2011 at 10:29:14PM +0530, Rabin Vincent wrote:
> > > On Fri, Nov 25, 2011 at 16:58, Dave Martin <dave.martin@linaro.org> wrote:
> > > > +#ifdef CONFIG_CPU_ENDIAN_BE8
> > > > +#define __opcode_to_mem_arm(x) swab32(x)
> > > > +#define __opcode_to_mem_thumb16(x) swab16(x)
> > > > +#define __opcode_to_mem_thumb32(x) swahb32(x)
> > > > +#else
> > > > +#define __opcode_to_mem_arm(x) (x) ((u32)(x))
> > > > +#define __opcode_to_mem_thumb16(x) ((u16)(x))
> > > > +#define __opcode_to_mem_thumb32(x) swahw32(x)
> > > > +#endif
> > > 
> > > The current kprobes code does:
> > > 
> > > #ifndef __ARMEB__ /* Swap halfwords for little-endian */
> > >                 bkp = (bkp >> 16) | (bkp << 16);
> > > #endif
> > > 
> > > There seems to be a difference between your macros and that for the case
> > > __ARMEB__ + !CONFIG_CPU_ENDIAN_BE8.  Or is that combination not
> > > possible?
> > > 
> > 
> > For building the kernel, it is effectively impossible, since you can't
> > have Thumb-2 code on BE32 platforms.  The opcode_to_mem_thumb*()
> > definitions are currently "don't cares" for this configuration in
> > my RFC, but we should probably clarify how things should behave in this
> > case.
> > 
> > The kprobes code does not look correct for the big-endian case, since
> > the bytes are not swapped -- this will result in big-endian words or
> > halfwords in memory, which would be correct for BE32 but not for BE8
> > (where instructions are always little-endian).
> > 
> > So they're both wrong, in different ways if I've understood correctly.
> > 
> > 
> > I'm not exactly sure how we handle BE32, or whether we need to.  I
> > believe that for BE32 it would be correct to leave the canonical
> > instruction completely un-swapped, because instructions are natively
> > big-endian on those platforms.  Note that BE32 is only applicable to
> > older versions of the architecture, and so Thumb-2 is not applicable to
> > any BE32 platform, so the only situation where we would care is if
> > kprobes, ftrace or similar allows breakpoints or tracepoints to be set
> > in userspace Thumb code on these platforms.
> > 
> > I think that __ARMEB__ encompasses any big-endian target including BE8
> > and BE32, so we might need to be a bit careful about how we use it.
> > 
> > 
> > Rabin, did the __opcode_read stuff look useful for ftrace?  My idea
> > was to factor out the logic of how to read/write a whole instruction,
> > but my proposal may be overkill...
> > 
> > 
> > Tixy, do you have a view on these issues?
> 
> I had to read the ARM ARM, I wasn't aware of BE8 :-)

BE8 is "the" big-endianness, at least since about ARMv6.  It's the only
form of big-endianness applicable to any CPU running Thumb-2.

Do you care about being to able to set probes in Thumb user code when
the kernel is not Thumb-2, or do you simply not support that scenario
at all?  (Thinking about, I'm guessing we don't currently support that?)


> My view is that the the current kprobes code just doesn't handle BE8.
> Anywhere where it reads the original instruction, writes a breakpoint or
> restores the instruction would need to swap the byte order. To do that,
> the proposed mem_to_opcode/opcode_to_mem helpers would be useful.
> 
> However, the read/write a whole instruction functions do look a bit
> overkill. Especially if the number of places using these is small, due
> to factorisations like Rabin's __patch_text().

I'm inclined to agree -- it seemed worthwhile to see how possible it was,
but while the idea being abstracted is straightforward enough, trying to
do this kind of thing using the C preprocessor is like trying to build
a stepladder out of custard.

Cheers
---Dave
Tixy (Jon Medhurst) Nov. 29, 2011, 2:06 p.m. UTC | #4
On Tue, 2011-11-29 at 10:42 +0000, Dave Martin wrote:
> Do you care about being to able to set probes in Thumb user code when
> the kernel is not Thumb-2, or do you simply not support that scenario
> at all?  (Thinking about, I'm guessing we don't currently support that?)

kprobes are only designed to probe kernel code, that's what the 'k'
stands for. ;-)
Dave Martin Nov. 29, 2011, 3:27 p.m. UTC | #5
On Tue, Nov 29, 2011 at 02:06:08PM +0000, Jon Medhurst (Tixy) wrote:
> On Tue, 2011-11-29 at 10:42 +0000, Dave Martin wrote:
> > Do you care about being to able to set probes in Thumb user code when
> > the kernel is not Thumb-2, or do you simply not support that scenario
> > at all?  (Thinking about, I'm guessing we don't currently support that?)
> 
> kprobes are only designed to probe kernel code, that's what the 'k'
> stands for. ;-)

Right, I think I may have been confusing that with some ftrace features.

Cheers
---Dave
Rabin Vincent Dec. 1, 2011, 5:26 p.m. UTC | #6
On Mon, Nov 28, 2011 at 05:41:22PM +0000, Dave Martin wrote:
> Rabin, did the __opcode_read stuff look useful for ftrace?  My idea
> was to factor out the logic of how to read/write a whole instruction,
> but my proposal may be overkill...

No.  ftrace just uses probe_kernel_read() and always reads a 32-bit
instruction.
Dave Martin Dec. 1, 2011, 5:59 p.m. UTC | #7
On Thu, Dec 01, 2011 at 10:56:40PM +0530, Rabin Vincent wrote:
> On Mon, Nov 28, 2011 at 05:41:22PM +0000, Dave Martin wrote:
> > Rabin, did the __opcode_read stuff look useful for ftrace?  My idea
> > was to factor out the logic of how to read/write a whole instruction,
> > but my proposal may be overkill...
> 
> No.  ftrace just uses probe_kernel_read() and always reads a 32-bit
> instruction.

Right -- fair enough.

---Dave
diff mbox

Patch

diff --git a/arch/arm/kernel/ftrace.c b/arch/arm/kernel/ftrace.c
index c46d18b..c437a9d 100644
--- a/arch/arm/kernel/ftrace.c
+++ b/arch/arm/kernel/ftrace.c
@@ -72,12 +72,13 @@  static int ftrace_modify_code(unsigned long pc,
unsigned long old,
 {
 	unsigned long replaced;

-#ifndef __ARMEB__
 	if (IS_ENABLED(CONFIG_THUMB2_KERNEL)) {
-		old = (old >> 16) | (old << 16);
-		new = (new >> 16) | (new << 16);
+		old = __opcode_to_mem_thumb32(old);
+		new = __opcode_to_mem_thumb32(new);
+	} else {
+		old = __opcode_to_mem_arm(old);
+		new = __opcode_to_mem_arm(new);
 	}
-#endif

 	if (old) {
 		if (probe_kernel_read(&replaced, (void *)pc, MCOUNT_INSN_SIZE))
diff --git a/arch/arm/kernel/insn.c b/arch/arm/kernel/insn.c
index f65b68c..9079997 100644
--- a/arch/arm/kernel/insn.c
+++ b/arch/arm/kernel/insn.c
@@ -27,7 +27,7 @@  __arm_gen_branch_thumb2(unsigned long pc, unsigned
long addr, bool link)
 	if (link)
 		second |= 1 << 14;

-	return (first << 16) | second;
+	return __opcode_thumb32_compose(first, second);
 }

 static unsigned long
diff --git a/arch/arm/kernel/patch.c b/arch/arm/kernel/patch.c
index 15df8a5..5398659 100644
--- a/arch/arm/kernel/patch.c
+++ b/arch/arm/kernel/patch.c
@@ -17,21 +17,21 @@  void __kprobes __patch_text(void *addr, unsigned int insn)
 	bool thumb2 = IS_ENABLED(CONFIG_THUMB2_KERNEL);
 	int size;

-	if (thumb2 && !is_wide_instruction(insn)) {
-		*(u16 *)addr = insn;
+	if (thumb2 && __opcode_is_thumb16(insn)) {
+		*(u16 *)addr = __opcode_to_mem_thumb16(insn);
 		size = sizeof(u16);
 	} else if (thumb2 && ((uintptr_t)addr & 2)) {
 		u16 *addrh = addr;

-		addrw[0] = insn >> 16;
-		addrw[1] = insn & 0xffff;
+		addrw[0] = __opcode_thumb32_first(insn);
+		addrw[1] = __opcode_thumb32_second(insn);

 		size = sizeof(u32);
 	} else {
-#ifndef __ARMEB__
 		if (thumb2)
-			insn = (insn >> 16) | (insn << 16);
-#endif
+			insn = __opcode_to_mem_thumb32(insn);
+		else
+			insn = __opcode_to_mem_arm(insn);

 		*(u32 *)addr = insn;
 		size = sizeof(u32);
@@ -61,7 +61,7 @@  void __kprobes patch_text(void *addr, unsigned int insn)
 		stop_machine(patch_text_stop_machine, &patch, cpu_online_mask);
 	} else {
 		bool straddles_word = IS_ENABLED(CONFIG_THUMB2_KERNEL)
-				      && is_wide_instruction(insn)
+				      && __opcode_is_thumb32(insn)
 				      && ((uintptr_t)addr & 2);

 		if (straddles_word)