凌云窟
南山巅上火麟烈,北海潜深雪饮寒。可怜两锋未缘见,雪刀封隐孤剑鸣。
<2010年3月>
28123456
78910111213
14151617181920
21222324252627
28293031123
45678910

留言簿(0)

随笔分类

随笔档案

文章档案

相册

简历下载

搜索

最新评论

阅读排行榜

评论排行榜

 
VC知识库BLOG   首页  新随笔  联系  聚合  登录 
  随笔-22 文章-0 评论-38 Trackbacks-0
2007年7月14日
     所谓满递减堆栈是指堆栈通过减小存储器的地址向下增长,堆栈指针指向内含有效数据项的最低地址。空递减堆栈是指堆栈通过减小存储器的地址向下增长,堆栈指针指向堆栈下的第一个空位置。
     那么x86是属于那一种呢?
     结论是x86的堆栈是满递减堆栈。

     我们经常遇到的堆栈操作就是函数调用时需要将函数的参数入栈,因此在调用函数的地方设置一个断点,然后查看汇编代码:

     这里Hello函数有一个默认值为9的参数.

在00401078这行代码执行后停住,然后打开vc的registers可以看到目前esp的值,这是一个内存地址。然后在Memory里面查看esp所值的内存的值,结果是刚好是9.


发表于 2007-07-14 23:17 莫问春秋 阅读(5952) | 评论 (2)编辑 收藏
2007年6月27日

  好久不写blog都生疏了,刚好公司要组织一次C++的基本功考试,就一个派生类调用虚函数的问题研究了一下编译器的实现:

class A
{
public:
    A()
{}
    
~A(){}
     
virtual void Display(){cout<<"A"<<endl;}
}
;

class B:public A
{
public:
    B()
{Display();}
    
~B(){}
    
virtual void Display(){cout<<"B"<<endl;}
}
;
int main()
{
   B 
*pBb = new B;
   delete pBb;
}
在B(){Display();}设定一个断点,跟踪到汇编代码得到如下代码:
00401391   mov         eax,dword ptr [ebp-10h]
00401394   mov         dword ptr [eax],offset B::`vftable' (00432020)
0040139A   mov         ecx,dword ptr [ebp-10h]
0040139D   call        @ILT+155(B::Display) (004010a0)
前三排的代码是设置虚函数表,然后直接调用了B::Display函数,并没有通过虚函数表间接调用这个函数。这说明在构造函数中虽然虚函数表已经设置好了,但是调用虚函数是不会使用虚函数表的,是直接调用.

下面的代码是非构造函数内经过虚函数表间接调用Display的汇编码:
0040127E   mov         edx,dword ptr [ebp-10h]
00401281   mov         eax,dword ptr [edx]
00401283   mov         esi,esp
00401285   mov         ecx,dword ptr [ebp-10h]
00401288   call        dword ptr [eax]
发表于 2007-06-27 22:33 莫问春秋 阅读(7331) | 评论 (5)编辑 收藏
2006年12月31日
      可以说是迫于压力吧,工作这几个月来发现linux的用途太广泛了,既然这样发时间研究minix操作系统为什么不研究linux ?操作系统理论很成熟的,都是在x86上变化自然不大。
       另外一个原因就是minix本身的问题,用它来研究x86硬件体系和基础操作系统实现很适合,但是某些高级功能是不可能在它里面找到的而linux有。
      没什么好说的了,Go  on!
发表于 2006-12-31 23:20 莫问春秋 阅读(4360) | 评论 (2)编辑 收藏
 
     今年认真读过的书: 
     << code complete >>  :  对于刚毕业的我来说,我认为正是这本书把我领进了软件工业的大门。
     << exception C++ style >>  : 对于C++爱好者来说,这本书让我更加深入了解C++语言。
    << 设计模式 >> : 做面向对象软件设计个人认为必看之书,还是比较好理解,不过要灵活运用恐怕需要多些时间。
    << 嵌入式实时操作系统uCOS-Ⅱ >> : 毕业设计是做操作系统的调度内核,认真研究了一下这本书。很高兴的一件事情,看过这本书后对操作系统产生了浓厚的兴趣。然后看了<<Operating System - Design and Implement>>(minix)的前几章,很可惜后来换工作就没有继续看下去了,后来考虑一些比较现实的问题就决定看linux。这样研究操作系统就成为我下半年的主要业余任务。
     个人比较喜欢收集经典书籍,所以这些书我都买下来了,其他的看过的电子书就不提了。
   
    工作上取得的进步:
    现在跟我刚出校门时简直没有什么变化,我还是需要时间和机会来证明自己的能力。我现在还是郁闷我的水平到底如何?

    感觉也没什么好总结的,最大的宽慰就是现在对x86硬件体系和操作系统在全局有了把握。很可惜的是,这个过程中我浪费了很多时间在game上,不然应该更好的。

    明年的事情在敲定工作后在打算吧。主要研究包括: linux操作系统源代码分析、C++深入研究、底层网络开发。
发表于 2006-12-31 22:51 莫问春秋 阅读(1895) | 评论 (0)编辑 收藏
2006年12月29日

1。符号查找(对于函数此时只看名字,不看参数)
    大致顺序是
    (1)如果有限定名( XXX:: )那么就直接在XXX里查找
    (2)函数局部名字空间
    (3)(如果是成员)类名字空间
    (4)递归向上至所有基类的名字空间
    (5)当前名字空间
    (6)递归向外至所有外层名字空间,
    在任一层里, 用using导入的符号和该层的其他符号同一对待。
    keonig查找: 对于函数, 如果参数为类/结构/模版类并位于其他的名字空间,
    在(5)和(6)的查找中导入该空间(不递归向外)的符号一同查找.

2。(如果是函数)重载决议(注意此时特化的函数不参与决议)

3。(如果是类内部的名字)检查访问权(注意此时特化的函数仍然不参与决议)

4。(如果找到了一个模版)模版特化决议

 

编译器执行以上步骤的时候是使用贪心匹配,只要找到一个符合当前检查内容的就会停止查

所以任何一层都有可能发生错误的掩盖情况

例1

 1 void  f( int ) {}
 2 class  Y
 3 {
 4 public :
 5      void  f() {}
 6     Y()
 7      {
 8         f( 1 );
 9     }

10 }
;


这里的f(2)在1.(2)这里找到了符号f,就不会向上到1.(5)查找到真正的f(int)了

例2

void  g( int ) {}
namespace  S
{
    
void  g() {}

    
void  h()
    
{
        g(
1 );
    }

}


这里的g(1)在1.(5)这里找到了符号g,就不会向上到1.(6)查找到真正的g(int)了

例3

class  Y
{
    
void  f( int ) {}   // [1]
public :
    
void  f( double ) {}   // [2]
}
;

int  main()
{
    Y y;
    y.f(
1 );
}


y.f(1)会调用[2]吗?不会,因为在第2步重载决议的时候就选定[1]了,因此这段代码会报
出无法访问private成员的错误

例4

template  < typename T >
void  f(T) {}   // [1]

template 
< typename T >
void  f(T * ) {}   // [2]

template 
<>
void  f < int *> ( int * ) {}   // [3]


int  main()
{
    
int   * =   0 ;
    f(p);
}


这里的f(p)会调用[3]吗?
不会,因为在进行到第二步重载决议的时候,只有[1]和[2]参与了重载决议,结果选择了
[2],那么[1]的特化版本[3]当然就轮不到了。

例5

class  X
{
    template 
< typename T >   void  g()  {}
public :
    template 
<>   void  g < int > () {}
}
;

int  main()
{
    X y;
    y.g
< int > ();
}


这里首先第3步访问检查发现g为private(此时g的特化版本被直接无视了),所以即使
g<int>为public, 该段代码仍然不能够编译通过

例6

namespace  E
{
    
class  X {} ;
    
void  f(X) {}   // [1]
}


void  f(E::X) {}    // [2]

class  X
{
public :
    
void  f() {}   // [3]
     void  g()
    
{
        E::X x;
        f(x); 
// [4]
    }

}
;

[4]会调用那个呢? 在1.(3)里就确定了是[3],因此参数不匹配
如果注释掉[3]的f,那么由于koenig查找, 在1.(5)里[1]和[2]都会是平等的可选项
所以会出现二义性.
如果把namespace E改为class E, 把E中的f改为静态函数
由于koenig查找仅仅导入参数的名字空间, 因此[1]将不参与1.(5)的查找,
最终结果会是[2]

发表于 2006-12-29 10:44 莫问春秋 阅读(2294) | 评论 (0)编辑 收藏
2006年12月25日
       毕业后10月份离开了实习的那家公司,原因很简单,公司根本就不是搞软件的。
       接下来开始重新找工作,所以就没有看minix了,花了一个月搞定了新工作,公司是搞操作系统的,心理自然很高兴。然后又开始上班,很可惜的是,公司让我去搞gui库,偏偏我对这个没丝毫兴趣。不过自问在那里呆的1个半月我还是认真负责的做自己的工作,并没有因为兴趣而懈怠。
       突然接到华为叫我去报道的电话通知 ,我觉得这是个机会啊,毅然决定去深圳。可是现在28号了,还没等到书面的报道通知 。打过几次电话给他们人事部,只有一个叫我等待的答复。 既然已经出来了,就打算在这边呆一呆 。不过老是这样等待让我觉得这个事情有点玄,所以我也开始投简历了。
       这边的同学都说这边机会多,这个我自然相信。不过经过毕业后的这几个月的经历,认识到找工作真的要慎重。自己的个性真的不喜欢总是换工作。既然已经出来了,就要找个自己能够长久适宜的工作。
       努力吧!!自己的路还很长啊。听这边的同学谈了一下深圳的情况,我和他们那些学长的差距还很大啊!!
发表于 2006-12-25 17:47 莫问春秋 阅读(4163) | 评论 (1)编辑 收藏
2006年9月4日

       以前我研究过ucosII的源代码,对应于x86来说,ucos是实模式下面的操作系统。而Minix是保护模式下的操作系统,自然他们的Context Switch有不一样的地方,现在,我就结合我知道的知识谈一下二者的区别。
      Context Switch是多任务操作系统必不可少的部分,它的主要任务就是保护cpu的执行现场,将cpu的寄存器保存起来。在这一点上Minix3 和ucosII是没有区别的。同时二者在实现的层面上都和堆栈有关。
      现在着重谈一下他们的区别:
      第一个首要关注的地方就是x86的硬件体系,保护模式下的内容远远比实模式丰富,研究保护模式下的操作系统,必然跟GDT、LDT、IDT发生关系。所以,保护模式下Conext Switch就要比实模式复杂些。
      ucosII在实现Context Switch时,仅仅将cpu的执行现场也就是寄存器保存在任务自己的堆栈中,保存和还原任务寄存器的功能都是通过中断机制完成的。保护模式下的Context Switch的内容就要丰富的多,首要提到的东西就是新增加的tr寄存器,tr寄存器保存的是任务状态段
TSS的段选择子。

struct tss_s 
{
  reg_t backlink;
  reg_t sp0;                    
/* stack pointer to use during interrupt */
  reg_t ss0;                    
/*   "   segment  "  "    "        "     */
  .
  .
}

struct tss_s tss;
tss.ss0 
= DS_SELECTOR;
init_dataseg(
&gdt[TSS_INDEX], vir2phys(&tss), sizeof(tss), INTR_PRIVILEGE);
  gdt[TSS_INDEX].access 
= PRESENT | (INTR_PRIVILEGE << DPL_SHIFT) | TSS_TYPE;

        可以看出,TSS段实际上就是保存一个tss_s结构。那tss_s又有什么用?
         首先需要说明,保护模式下发生Context Switch时,cpu寄存器并不是保存在自己的工作堆栈中的,而是保存在进程控制块中的。
struct proc 
{
  
struct stackframe_s p_reg;    /* process' registers saved in stack frame */
  .
  .
}
        tss_s的两个最重要的成员就是sp0和ss0。ss0用来指明任务控制块位于数据段内,sp0就指向p_reg的最高位,因为x86的堆栈是向下增加的。
       这样,当中断发生时,通过tr就能够找到tss的sp0和ss0,然后就能够寻址到p_reg,中断将自动压入一些寄存器的指,另外一些则需要我们来压入,如下通过save函数。以下是一个简化的中断处理过程。
 call    save            
    .                          
    ret           
!为什么是ret而不是iret,因为ret会跳到_restart然后iret(?push    _restart?)

相关代码如下:

    .align    
16
save:
    cld            
! set direction flag to a known value
    pushad            
! save EAX, ECX, EDX, EBX, original ESP, EBP,
                                
! ESI, and EDI registers
    o16    push    ds        
! save ds
    o16    push    es        
! save es
    o16    push    fs        
! save fs
    o16    push    gs        
! save gs
    mov    dx, ss        
! ss is kernel data segment, tss.ss0 = DS_SELECTOR
    mov    ds, dx        
! load rest of kernel segments
    mov    es, dx        
! kernel does not use fs, gs
    mov    eax, esp    
! prepare to return
    incb    (_k_reenter)    
! from -1 if not reentering
    jnz    set_restart1    
! stack is already kernel stack
    mov    esp, k_stktop   
!
    push    _restart    
! build return address for int handler
    xor    ebp, ebp    
! for stacktrace
    jmp    RETADR
-P_STACKBASE(eax) !堆栈sp已经被改变了,所以只能够通过硬寻址找到返回地址

    .align    
4
set_restart1:
    push    restart1    
!存在中断嵌套
    jmp    RETADR
-P_STACKBASE(eax)
    
_restart:

! 找到更高优先级的任务,并将相应任务控制块的p_reg赋值给tss.sp0 

    cmp    (_next_ptr), 
0        ! see if another process is scheduled
    jz    0f
    mov     eax, (_next_ptr)
    mov    (_proc_ptr), eax    
! schedule new process 
    mov    (_next_ptr), 
0
0:    mov    esp, (_proc_ptr)    ! will assume P_STACKBASE == 0
    lldt    P_LDT_SEL(esp)        
! enable process' segment descriptors 
    lea    eax, P_STACKTOP(esp)    ! arrange for next interrupt
    mov    (_tss
+TSS3_S_SP0), eax    ! to save state in process table
restart1:
    decb    (_k_reenter)
    o16    pop    gs
    o16    pop    fs
    o16    pop    es
    o16    pop    ds
    popad
    add    esp, 
4        ! skip return adr(?call save?)
    iretd            
! continue process

-----------------------------------------------------------------------------------------------------------
      如果随笔中有什么错误,还请大家指教。谢谢!
发表于 2006-09-04 15:57 莫问春秋 阅读(6096) | 评论 (2)编辑 收藏
2006年8月31日
  这只是mpx386.asm文件的一部分内容,随着学习的深入我会把余下部分的注解也写下来。不过有一点很抱歉,我的这个注解主要是写给自己看,以免以后遗忘后无从下手。所以注解未免太个人化,不太容易懂。敬请谅解!

!在阅读这份代码之前,必须了解cpu的保护模式、GDT、LDT、IDT等相关概念。
.sect .text
!*===========================================================================*
!*                MINIX                         *
!*===========================================================================*
MINIX:                
! this is the entry point for the MINIX kernel
    jmp    over_flags    
! skip over the next few bytes
    .data2    CLICK_SHIFT    
! for the monitor: memory granularity
flags:
    .data2    
0x01FD        ! boot monitor flags:
                
!    call in 386 mode, make bss, make stack,
                
!    load high, don't patch, will return,
                !    uses generic INT, memory vector,
                
!    new boot code return
    nop            
! extra byte to sync up disassembler
over_flags:

!在minix中将引导kernel也就是MINIX标签前的执行代码称之为mointor。
!The monitor sets CS to the kernel code, DS to kernel data, ES to a "flat 
!4G" descriptor that address all memory simply from 0 up, and SS still 
!points to the monitor's stack. 

! Set up a C stack frame on the monitor stack.  (The monitor sets cs and ds
! right.  The ss descriptor still references the monitor data segment.)
    movzx    esp, sp        
! monitor stack is a 16 bit stack
    push    ebp
    mov    ebp, esp
    push    esi
    push    edi
    cmp    
4(ebp), 0    ! monitor return vector is
    jz    noret        
! nonzero if return possible
    inc    (_mon_return)
noret:    mov    (_mon_sp), esp    
! save stack pointer for later return

! Copy the monitor global descriptor table to the address space of kernel and
! switch over to it.  Prot_init() can then update it with immediate effect.

    sgdt    (_gdt
+GDT_SELECTOR)        ! get the monitor gdtr
    mov    esi, (_gdt
+GDT_SELECTOR+2)    ! absolute address of GDT
    mov    ebx, _gdt            
! address of kernel GDT
    mov    ecx, 
8*8            ! copying eight descriptors
    
!_gdt是位于kernel数据段内的GDT。
    
!GDT_SELECTOR(16bits)是GDT的段选择子,他的高13位是段描述符在GDT/LDT中的下标索引。
    
!sgdt读出gdtr寄存器到操作数地址,gdtr = 32bitsbaseAddr +16bitsLimit。
    
!(_gdt+GDT_SELECTOR+2= gdt's baseAddr
copygdt:
 eseg    movb    al, (esi)          
!esi保留的是monitor的GDT的偏移地址。它不位于kernel数据段内。所以es:si
                                   
!ES to a "flat 4G" descriptor that address all memory simply from 0 up
    movb    (ebx), al
    inc    esi
    inc    ebx
    loop    copygdt
    mov    eax, (_gdt
+DS_SELECTOR+2)    ! base of kernel data. _gdt+DS_SELECTOR+2 = 段基址的低24位
    and    eax, 
0x00FFFFFF            ! only 24 bits
    add    eax, _gdt            
! eax = vir2phys(gdt)
    mov    (_gdt
+GDT_SELECTOR+2), eax    ! set base of GDT
    lgdt    (_gdt
+GDT_SELECTOR)        ! switch over to kernel GDT
    
!GDTR内是GDT的物理地址,而_gdt位于kernel的数据段内,所以
    
!kernel's GDTR = kernel数据段的基地址+_gdt偏移地址

! Locate boot parameters, set up kernel segment registers and stack.
    mov    ebx, 
8(ebp)    ! boot parameters offset
    mov    edx, 
12(ebp)    ! boot parameters length
    mov    eax, 
16(ebp)    ! address of a.out headers
    mov    (_aout), eax
    mov    ax, ds        
! kernel data
    mov    es, ax
    mov    fs, ax
    mov    gs, ax
    mov    ss, ax
    mov    esp, k_stktop    
! set sp to point to the top of kernel stack

! Call C startup code to set up a proper environment to run main().
    push    edx
    push    ebx
    push    SS_SELECTOR
    push    DS_SELECTOR
    push    CS_SELECTOR
    call    _cstart        
! cstart(cs, ds, mds, parmoff, parmlen)
    add    esp, 
5*4

! Reload gdtr, idtr and the segment registers to global descriptor table set
! up by prot_init().

    lgdt    (_gdt
+GDT_SELECTOR)
    lidt    (_gdt
+IDT_SELECTOR)

    jmpf    CS_SELECTOR:csinit
csinit:
    o16    mov    ax, DS_SELECTOR
    mov    ds, ax
    mov    es, ax
    mov    fs, ax
    mov    gs, ax
    mov    ss, ax
    o16    mov    ax, TSS_SELECTOR    
! no other TSS is used
    ltr    ax                      
! LTR指令是专门用于装载任务状态段寄存器TR的指令。
                                
!该指令的操作数是对应TSS段描述符的选择子。LTR指令
                                
!从GDT中取出相应的TSS段描述符,把TSS段描述符的基地址
                                
!和界限等信息装入TR的高速缓冲寄存器中。 
    push    
0            ! set flags to known good state
    popf                
! esp, clear nested task and int enable

    jmp    _main            
! main()

发表于 2006-08-31 16:42 莫问春秋 阅读(2455) | 评论 (0)编辑 收藏
2006年8月30日
      好几个月前就已经决定研究一下操作系统实现,本来对于我这种初学者来说,linux应该是最适合我的了。可我无意中发现原来和linux有点关系血缘关系的minix系统,我无意抬高或贬低二者任何一个,我之所以选择minix,那是因为相对来说,我更喜欢学院派的东西。
     阅读的难度相对来说,阅读linux低版本内核的难度比minix要低些。一位前辈说,大概读minix花的时间会比linux低版本内核多2倍的时间。那是因为linux源代码的阅读人群比较多,注释工作已经做得很好了。相反,minix在国内的学习人群比较少,中文化注释很少。
发表于 2006-08-30 15:40 莫问春秋 阅读(8135) | 评论 (4)编辑 收藏
2006年8月11日

An assignment operator shall be implemented by a non-static member function with exactly one parameter.Because a copy assignment operator operator= is implicitly declared for a class if not declared by the user ,a base class assignment operator is always hidden by the copy assignment operator of the derived class.
                                                --- 摘自 [ANSI-C++ 1998 Standard]

下面有一段代码:

class Base 
{
public:
    
void operator =(const Base & right)
    
{
        cout
<<"Base's  operator ="<<endl;
    }

}
;

class Derd:public Base
{
public:
    
void operator =(const Base & right)
    
{
        cout
<<"Derd's operator ="<<endl;
    }


}
;

int main(int argc, char* argv[])
{
    Derd lDerd;
    Derd rDerd;
    lDerd 
= rDerd;
    
return 0;
}

这段代码的结果是:
Base's operator =
按照C++标准的说明,基类的operator =应该被hidden了,可是这里为什么还是这个结果?察看汇编代码发现,实际上Base的operator=间接的在派生类的implicitly-declared Derd::operator=中被调用。问题就来了,我明明已经在派生类中自己声明了一个operator=的啊!我翻看了一些资料,都说在你自己不声明任何复制赋值操作符的时候,编译器会为你隐式生成一个,可是这儿我已经声明了一个,为什么编译器还要声明复制赋值操作符?  
    在几位兄台的提点下,我仔细了搜索了下C++标准,查找了相关内容。我的错误大概是我没搞清楚复制赋值操作符的声明形式,A user-declared copy assignment operator X::operator= is a non-static non-template member function of class X with exactly one parameter of type X, X&, const X&, volatile X& or const volatile X&。从这一点来看,void operator =(const Base & right)根本不是Derd的复制赋值操作符,所以编译器隐式声明了一个void operator =(const Derd & right)。请大家在帮我分析下这个结论有问题不。
发表于 2006-08-11 14:34 莫问春秋 阅读(4514) | 评论 (6)编辑 收藏