Liuw's Thinkpad

想要赢就先学会输,想要成功就先学会失败

Archive for the ‘gcc’ tag

GCC嵌入式ASM快速指南

without comments

本文从Inline assembly for x86 in Linux中摘译。

1 简要GNU汇编语法

1.1 寄存器命名

寄存器名字前面应该加上%前缀。比如要使用eax,那么应该写为%eax。

1.2 来源和目的的顺序

在任何一条指令中,总是先写来源,然后再写目的。这与Intel汇编语法正好相反。

movl %eax, %ebx -- transfers the contents of eax to ebx

1.3 操作数的尺寸

根据操作数的长度是字节、字或者长整形,指令一般需要加上b、w或者l后缀。这不是强制要求的,GCC会根据操作数的尺寸自动加上合适后缀。但是手工加上后缀一方面加强了可读性,另一方面消除了GCC猜错的可能。

movb %al, %bl
movw %ax, %bx
movl %eax, %ebx

1.4 直接操作数

直接操作数需要加上$前缀。

movl $0xffff, %eax

1.5 直接内存引用

使用()去完成直接内存引用。

movb (%esi), %al -- will transfer the byte in the memory pointed by esi to al

2 内联汇编

GCC提供了一种特殊结构去创建联汇编,格式如下:

asm ( assembler template
    : output operands
    : input operands
    : list of clobbered registers
    );

模板由汇编指令构成。input operands是用来作为汇编指令输入操作数的C表达式。output operands依此类推。

asm ("movl %%cr4, %0\n" :"=r"(cr3val))

a    %eax
b    %ebx
c    %ecx
d    %edx
S    %esi
D    %edi

2.1 内存操作数约束

当操作数位于内存中时,所有操作都会直接在内存位置执行。内存操作数约束的作用是当一个C变量需要被内联汇编操作时,程序员不需要显式地把它放到寄存器中。如:

asm ("sidt %0\n" : :"m"(loc));

2.2 匹配约束

在某些情况下,一个变量可能会同时输入和输出操作数。这样的情况可以使用匹配约束来完成。

asm ("incl %0" :"=a"(var):"0"(var));

在我们的匹配约束例子中,%eax同时作为输入和输出操作数。var先被读入到%eax,操作完成后结果被存回var。“0”指定了和0号输出变量同样约束。即是说,它指定了var的输出应该只被放到%eax。这类约束可以在如下的情况使用:

  • 输入是一个变量然后输入到同一个变量;
  • 不需要分别使用不同的变量去保存输入和输出。

使用匹配约束可以有效地使用寄存器。

2.3 常见内联汇编用例

下面的例子展示常见的用法。

2.3.1 “asm”和寄存器约束“r”

int main(void)
{
        int x = 10, y;

        asm ("movl %1, %%eax;"
             "movl %%eax, %0;"
             :"=r"(y) /* y is output operand */
             :"r"(x)  /* x is input operand */
             :"%eax");/* eax is clobbered register */
        return 0;
}

生成的汇编代码如下:

main:
        pushl %ebp
        movl %esp, %ebp
        subl $8, %esp
        movl $10, -4(%ebp)
        movl -4(%ebp), %edx /* x=10 is stored in %edx */
,#APP
        movl %edx, %eax /* x is moved to eax */
        movl %eax, %edx /* y is allocated in edx and updated */
,#NO_APP
        movl %edx, -8(%ebp) /* value of y in the stack is updated with the value in edx */

“r”指明GCC可以采用任意的寄存器。在上例中,edx被先后用于存放x和y。

由于eax在clobbered list中,GCC不会使用它去存放数据。

在上例中,edx被先后用于输入和输出,这里有一个前提就是输入数据总是在输出之前就已经失效了。但是实际情况中有很多指令并不总是这样做的,所以我们可以加上“&”修饰符让输入输出使用不同的寄存器。

int main(void)
{
        int x = 10, y;

        asm ("movl %1, %%eax;"
             "movl %%eax, %0;"
             :"=r"(y) /* y is output operand, note the "&" modifier */
             :"r"(x)  /* x is input operand */
             :"%eax");/* eax is clobbered register */
        return 0;
}

生成下面代码:

main:
        pushl %ebp
        movl %esp, %ebp
        subl $8, %esp
        movl $10, -4(%ebp)
        movl -4(%ebp), %ecx /* x=10 is stored in %ecx */
,#APP
        movl %ecx, %eax
        movl %eax, %edx /* output is in %edx */
,#NO_APP
        movl %edx, -8(%ebp)

2.3.2 指定寄存器约束

下面例子中,cpuid的输入由eax指定,输出到eax、ebx、ecx、edx四个寄存器中。

asm ("cpuid"
     :"=a"(_eax),
      "=b"(_ebx),
      "=c"(_ecx),
      "=d"(_edx)
     :"a"(op));

然后生成如下的代码(这里假设eax等变量存放栈上):

        movl -20(%ebp),%eax     /* store 'op' in %eax -- input */
,#APP
        cpuid
,#NO_APP
        movl %eax,-4(%ebp)      /* store %eax in _eax -- output */
        movl %ebx,-8(%ebp)      /* store other registers in
        movl %ecx,-12(%ebp)        respective output variables */
        movl %edx,-16(%ebp)

strcpy可以用下面的方法实现:

asm ("cld\n rep\n movsb"
     : /* no input */
     :"S"(src), "D"(dst), "c"(count));

2.3.3 匹配约束

拿系统调用的代码做例子:

,#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) \
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
        : "=a" (__res) \
        : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
          "d" ((long)(arg3)),"S" ((long)(arg4))); \
__syscall_return(type,__res); \
}

通过使用匹配约束“0”,系统调用号被放入到eax中。

2.3.4 内存操作数约束

考虑如下的原子减一操作:

__asm__ __volatile__(
        "lock; decl %0"
        :"=m"(counter)
        :"m"(counter));

会生成如下的汇编:

,#APP
        lock
        decl -24(%ebp)
,#NO_APP

上面的例子中,变量在内存中直接被减一。假如使用“r”的话,操作就没有原子性了。

2.3.5 clobbered约束

考虑如下的memcpy实现:

asm ("movl $count, %%ecx;
      up: lodsl;
      stosl;
      loop up;"
     :
     :"S"(src), "D"(dst)
     :"%ecx", "%eax");

lodsl和stosl隐式地使用eax,ecx被显式地加载到寄存器中。除非我们用clobbered约束告诉GCC这两个寄存器是可用的,它不会再使用它们去存放其他数据。esi和edi没有加入到clobbered列表中,因为它们已经出现在了输入操作数列表中。准则就是,例如一个寄存器在asm中使用了(无论是显式还是隐式),而又没有出现在输入、输出操作数列表中,那么它就必须出现在clobbered列表中。

Author: Wei LIU
<liuw at liuw dot name>

HTML generated by org-mode 6.21b in emacs 23

Written by liuw

December 20th, 2010 at 10:59 am

Posted in 分享

Tagged with , , ,

locale惹的祸

without comments

换了一台新机器去做TPM硬件驱动的开发,于是从远程仓库把代码clone下来。试着把代码编译一次,出问题了,满版的错误,费点时间看了一下,其实是stddef.h没有找到造成的。

新机器和原来的机器都是一样系统,工具链也是一样的,代码也已经在我原来的机器上编译通过了。用find找了一下stddef.h,在机器上也是有的。仔细对比了一下通过和不通过的CFLAGS,发现不通过的情况下,头文件路径中没有gcc的包含路径,问题就出在这里了。

再看Makefile,gcc路径是通过如下的方法取得的。

GCC_INSTALL = $(shell gcc --print-serarch-dirs | sed -n -e 's/install: \(.*\)/\1/p')

使用sed匹配了以install开头的行。

但是,我现在这个系统的locale是中文的,所以“install”没有出现,取而代之的是“安装”,匹配不成功,GCC_INSTALL就成了空值,那么也没有把路径加入。

追踪了半天,没想到是locale出了问题。因为我原来一直用的是英文系统,所以没有遇到过这种情况。所以以后装系统,最好还是用英文的locale,不是说崇洋媚外不喜欢中文,要是遇到我今天的情况,估计还是挺恶心的,看着满版的错误,挺郁闷的。

Written by liuw

June 15th, 2010 at 12:28 am

Posted in Programming,UNIX-like

Tagged with , , ,

The Story of “likely()” and “unlikely()”

without comments

“likely()” and “unlikely()” are macros defined in Linux kernel. But the true story is about optimization.

#define likely(x)   __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

These macros can be used in user space, though. Because they are in fact some built-in features of gcc(1). gcc(1) should optimize assembly generation according to program’s need.

Try this simple sample.

int main()
{
    int a;
    if (likely(a == 100))
        printf("Branch 1\n");
    else
        printf("Branch 2\n");
    return 0;
}

I use an uninitialized variable, that’s a bug, but this does not affect my demostration. Feel free to ignore that uninitialized “a”, we just need something to compare to a constant, don’t we? If I use two constants for comparison, gcc(1) will optimize in compile time and we can not see the outcoming of “likely()” and “unlikely()”.

Use gcc(1) to generate assembly.

$ gcc -S -O2 likely.c

Here is what we get. Ignore less important part, focus on most valuable snippet.

    cmpl $100, %eax
    jne  .L2
    movl $.LC0, (%esp)
    call puts
.L4:
    addl $4, %esp
    xorl %eax, %eax
    popl %ecx
    popl %ebp
    leal -4(%ecx), %esp
    ret
.L2:
    movl $LC1, (%esp)
    call puts
    jmp  .L4

Pay attention to that “jne” directive. We compare variable (which stores in eax) with 100 to see if they are equal. The outcoming result is “likely” to be true, so that “jne” is not executed, the code just falls through. The benifit we get is that no jumping means no pipe line flush, we can make full use of directive cache.

If I change “likely” to “unlikely” in my C code, assembly code generation will be different. However, it should follow the same rule as above – make full use of pipe line.

Written by liuw

December 14th, 2009 at 11:07 am

Type-checking in gcc

without comments

In linux/kernel.h, there is such a macro

/*
 * min()/max() macros that also do
 * strict type-checking.. See the
 * "unnecessary" pointer comparison.
 */
#define min(x,y) ({ \
        typeof(x) _x = (x); \
        typeof(y) _y = (y); \
        (void) (&_x == &_y); \
        _x < _y ? _x : _y; })

In order to do strict type-checking, that “unnecessary” pointer comparison is, however, necessary. If we compare two distinct pointers without a cast, gcc will complain about it, so that we are able to figure out where things go wild.

To go further, we need some knowledge about Type System, which means “a tractable syntactic method for proving the absence of certain program behaviors by classifying phrases according to the kinds of values they compute”. That requires an understanding of computer language theories, which is beyond my knowledge now. I shall figure it out in the future.

http://en.wikipedia.org/wiki/Type_system

Written by liuw

December 7th, 2009 at 4:09 pm