凌云窟
南山巅上火麟烈,北海潜深雪饮寒。可怜两锋未缘见,雪刀封隐孤剑鸣。
<2008年10月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

留言簿(0)

随笔分类

随笔档案

文章档案

相册

简历下载

搜索

最新评论

阅读排行榜

评论排行榜

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

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

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

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


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

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

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