0

This is a code I used to write some data in memory for debugging (until printf is available in u-boot program). Variable myptr is located in .__mydebug section and it is incremented by 8 after every 8-byte write and I want to write any value I'm interested in in the form of {debug_tag, debug_value} pair. Here debug_tag is some value to show the debug data sequence, and the debug_value is the value I want to check(or see) during the debug. This is arm64 assembly.

.global myptr

ldr x28, =myptr   /* load the address of myptr */
add x28, x28, #8  /* set write pointer to the next address after the myptr variable */
mov x27, #0x33   /* first debug write starts with tag value 0x33 */
str x27, [x28], #8   /* write the tag value, increment the pointer */
mov x27, some_value   /* some_value : the value I want to see with tag value 0x33 */
str x27, [x28], #8   /* write the debug value, increment the pointer */    ldr x26, =myptr    /* load pointer address */


/* next debug write, in the same assembly code, x28 hasn't changed, so use as is */
mov x27, #0x34  /* new debug tag */
str x27, [x28], #8  /* write new tag, increment pointer */
mov x27, some_another_value  /* another data I want to check */
str x27, [x28], #8  /* write the data, increment pointer */
ldr x26, =myptr  /* load the address of myptr to x26 */
str x28, [x26]   /* save the updated pointer in myptr, just in case x28 is modified and \
                   the pointer should be used later in assembly or C code .. */
.... (skip) ....

.section .__mydebug
myptr: .double 0x0
data_start: .double 0x0

So this is sequentially writing debug info in a memory section which works.
I can continue this debug write later in a .c program as below and it also works.

// debug print 
int xx=sizeof(struct global_data);
*((uint64_t *)myptr) = 0x101; myptr+=8;  /* debug tag start with 0x101 here */
*((uint64_t *)myptr) = xx; myptr+=8;     /* write some data I want to check.. */
*((uint64_t *)myptr) = 0x102; myptr+=8;  /* another debug tag */
*((uint64_t *)myptr) = base; myptr+=8;    /* another value I want to check */

Ok, I can live with that. But this doesn't look nice and inconvenient.
So I'm curious how I can do the above in C program using a function with inline assembly. I want to pass to the function the tag value and the debug value(64-bit) as arguments. The function should retrieve the myptr value to write the tag and data and should update the myptr value each time. I tried writing a function below.

void dbg_print(unsigned int tag, uint64_t data)
{
    uint64_t ptr_addr1;
__asm (
    "ldr %[ptr_addr], =myptr" \
    "ldr %[ptr_val], [%[ptr_addr]]" \
    "str %[tag_val], [%[ptr_val]], #8" \
    "str %[data_val], [%[ptr_val]], #8"
    : /* no output */ \
    : [tag_val] "r" (tag), [data_val] "r" (data) /* input list */ \
    : "memory" /* no specific clobbered register, but memory modified */
);
}

When I compile it, I get this compile error.

common/init/board_init.c: In function 'dbg_print':
common/init/board_init.c:144:1: error: undefined named operand 'ptr_addr'
  144 | );
      | ^
common/init/board_init.c:144:1: error: undefined named operand 'ptr_val'
common/init/board_init.c:144:1: error: undefined named operand 'ptr_addr'
common/init/board_init.c:144:1: error: undefined named operand 'ptr_val'
common/init/board_init.c:144:1: error: undefined named operand 'ptr_val'
make[2]: *** [scripts/Makefile.build:254: spl/common/init/board_init.o] Error 1
make[1]: *** [scripts/Makefile.spl:515: spl/common/init] Error 2

I can't understand undefined named operand error. Do I need to define the operand in the template somewhere? In the example in https://www.keil.com/support/man/docs/armclang_ref/armclang_ref_qbn1517569205870.htm, the operands in the template are just used without defining. The variables in C is declared anyway but aren't the operands in the assembly template substituted by the compiler anyway?
Thank you for reading and I would be grateful if someone could clarify this thing to me.

ADD :
Having read Nate Eldredge and Peter Cordes's comments, I realized 'defining the operand' means to connect the value into the C world using the operand specifiers (those fields after : and connected with :). So I tried changing the code to this and I'll see if this works.

void dbg_print(unsigned int tag, uint64_t data)
{
    uint64_t ptra, ptrv;
__asm (
    "ldr %[ptr_addr], =myptr \n"   /* get pointer address */
    "ldr %[ptr_val], [%[ptr_addr]] \n"    /* get pointer value */
    "str %[tag_val], [%[ptr_val]], #8 \n"   /* write to pointed addr */
    "str %[data_val], [%[ptr_val]], #8 \n"   /* write to pointed addr */
    : [ptr_addr] "=r" (ptra), [ptr_val] "=r" (ptrv)
    : [tag_val] "r" (tag), [data_val] "r" (data) /* input list */
    : "memory" /* no specific clobbered register, but memory modified */
    );
}
Chan Kim
  • 5,177
  • 12
  • 57
  • 112
  • 1
    I'm not sure why you want to use inline assembly for simple loads and stores that the compiler can do perfectly well. You could write it more cleanly as `volatile uint64_t *p = (volatile uint64_t *)myptr; p[0] = 0x101; p[1] = xx; p[2] = 0x102; p[3] = base;` Or wrap it in a nice function or macro. – Nate Eldredge Dec 10 '21 at 05:51
  • 1
    In the example, the operands in the template are NOT just used without defining; the names `lhs` and `rhs` in the template are matched to the names specified for the operands at the end. Your code didn't define any operand called `ptr_addr` so it doesn't work. It won't automatically be matched against a program variable with that name, if that's what you were wondering. – Nate Eldredge Dec 10 '21 at 05:54
  • 1
    Actually should be `volatile uint64_t *p = (volatile uint64_t *)&myptr;` if I understand the code correctly. – Nate Eldredge Dec 10 '21 at 05:58
  • 1
    The named operands you specified are `[data_val]` and `[tag_val]`, not `[ptr_addr]` and `[ptr_val]`, so the error message seems pretty obvious; GCC doesn't know how to expand `%[ptr_addr]` because you didn't give any of the operands that name in the Extended Asm statement. As Nate says, the best fix is probably to replace the inline-asm statement entirely, though. Especially since you have bugs like writing a register without having specified any `"=r"` output operands, only `"r"` inputs (which implies read-only). – Peter Cordes Dec 10 '21 at 06:04
  • Hi, Nate and Peter, thank you for the comments. I've got helps from you many times before( :)) . I updated my question and will try if it works. by the way I just wanted to use function using inline assembly for this case. (to understand more clearly). – Chan Kim Dec 10 '21 at 06:27
  • That probably works, although note that you're telling the compiler those registers are inputs as well as output (` "+r"` instead of `"=r"`), so you are reading uninitialized `ptr` and `ptrv`. That's technically UB, and GCC or clang should warn you about it, although it's likely harmless here. You also have a bunch of unnecessary \ line-continuation characters. You don't need any; string-literal concatenation over whitespace works with any whitespace, and the `:` separators don't care about newlines. – Peter Cordes Dec 10 '21 at 06:34
  • It's still looks over-complicated vs. an `"=m"(*myptr)` output operand (with offsets, not post-increment), and pointless vs. `(volatile foo*)myptr->tag = tag;` and so on (I think), assuming you have it declared as a pointer-to-struct. – Peter Cordes Dec 10 '21 at 06:41
  • I slightly changed the code in `ADD` as above. Tried this but writing data using dbg_print has no effect. like nothing happens and goes to next codes. (with +r or =r either). BTW, ptra register is first written and then used as input (for ldr instr.) in the next instruction. In this case In the first instr. it should be "=r" but in the second, it should be just "r". How should specify it? ANd just found the dbg_print function is not shown in the .map file, like optimzed out?. Can anyone give me some thoughts? Thanks! – Chan Kim Dec 10 '21 at 09:55
  • `"=r"` describes it as an output from the entire asm statement. As far as the compiler is concerned, *it* doesn't need to place any value in that register before your string of asm instructions runs, and after that the register will contain the value of a variable. But yes, since you write that operand before reading others, it should actually be `"=&r"` to declare it as "early clobber". Also, you'll need `asm volatile` to make sure it's not optimized away even if later C code doesn't use the output operands. (With no output operands, it's implicitly `volatile`.) – Peter Cordes Dec 10 '21 at 13:42

1 Answers1

0

I can't understand undefined named operand error. Do I need to define the operand in the template somewhere? In the example in https://www.keil.com/support/man/docs/armclang_ref/armclang_ref_qbn1517569205870.htm, the operands in the template are just used without defining.

Yes, you need to define them either in the inputs or the outputs of the asm statement. The examples you link all define all the names they use. Your code just defines tag_val and data_val there, so ptr_val and ptr_addr are undefined.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • I leared it from Peter Cordes's comment but because this was original question, I select it as an answer. Thanks! – Chan Kim Dec 11 '21 at 08:14