source: soft/giet_vm/memo/src/memo.cpp @ 250

Last change on this file since 250 was 238, checked in by alain, 11 years ago

Major evolution to support physical addresses larger than 32 bits.
The map.xml format has been modified: the vsegs associated to schedulers
are now explicitely defined and mapped in the page tables.

File size: 20.8 KB
RevLine 
[163]1/* -*- c++ -*-
2 *
[238]3 * GIET_VM_LGPL_HEADER_BEGIN
[163]4 *
[238]5 * This file is part of GIET_VM, GNU LGPLv2.1.
[163]6 *
[238]7 * GIET_VM is free software; you can redistribute it and/or modify it
[163]8 * under the terms of the GNU Lesser General Public License as published
9 * by the Free Software Foundation; version 2.1 of the License.
10 *
[238]11 * GIET_VM is distributed in the hope that it will be useful, but
[163]12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
[238]17 * License along with GIET_VM; if not, write to the Free Software
[163]18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 * 02110-1301 USA
20 *
[238]21 * GIET_VM_LGPL_HEADER_END
[163]22 *
23 * Copyright (c) UPMC, Lip6, SoC
24 *         Mohamed Lamine KARAOUI <Mohamed.Karaoui@lip6.fr>, 2012
25 */
26
27#include <string.h>
28#include <cassert>
29#include <sstream>
30#include <fstream>
31#include <ios>
32#include <iostream>
33#include <cstdarg>
34#include <cassert>
35#include <iomanip>
36
37
38#include "exception.h"
39#include "memo.h"
40
[215]41//#define MOVER_DEBUG
[163]42
[238]43////////////////////////////////////////////
44MeMo::MeMo( const std::string     &filename, 
45            const size_t          pageSize)
46        : m_path(filename),
47          m_pathHandler(filename),
48          m_ginit(false),
49          m_generator(new elfpp::object())
[163]50{
51    PSeg::setPageSize(pageSize);
52
[238]53    // loading map_info blob
[173]54    m_data = std::malloc(bin_size(m_path));
55    if ( !m_data )
56        throw exception::RunTimeError("malloc failed... No memory space");
[163]57
[173]58    m_size = load_bin(m_path, m_data);
59
[238]60    // checking signature
[173]61    mapping_header_t*   header = (mapping_header_t*)m_data; 
62    if((IN_MAPPING_SIGNATURE != header->signature))
63    {
[238]64        std::cout  << "wrong signature " 
65                   << std::hex <<header->signature
66                   << ", should be:"<< IN_MAPPING_SIGNATURE << std::endl;
[173]67        exit(1);
68    }
69
[163]70#ifdef MOVER_DEBUG
71    std::cout << "Binary file path: " << m_path << std::endl;
[165]72    print_mapping_info(m_data);
[163]73#endif
74
75    buildMap(m_data);
76   
77#ifdef MOVER_DEBUG
78    std::cout << "parsing done" << std::endl ;
79#endif
80
81#ifdef MOVER_DEBUG
82    print_mapping();
83    std::cout << *this << std::endl;
84#endif
85
86    m_psegh.check();
87#ifdef MOVER_DEBUG
88    std::cout << "checking done" << std::endl ;
89#endif
90
91}
92
[238]93/////////////////////////////////////////
[163]94void MeMo::print( std::ostream &o ) const
95{
96    std::cout << "All sections:" << std::endl;
97    FOREACH( sect, m_generator->get_section_table() )
98    {
99        assert(&*sect != NULL);
100        std::cout << *sect << std::endl; 
101    }
102}
103
[238]104////////////////////////////////
[163]105void MeMo::print_mapping() const
106{
107    std::cout << m_psegh << std::endl;
108}
109
[238]110////////////////////////////////////////////////////////////////////////////////
[163]111elfpp::section* MeMo::get_sect_by_addr(elfpp::object *loader, unsigned int addr)
112{
113    FOREACH( sect, loader->get_section_table() )
114    {
115        assert(&*sect != NULL);
116        elfpp::sh_flags_e eflags = sect->get_flags();
117        if ( !(eflags & elfpp::SHF_ALLOC) )
118            continue;
119        if(sect->get_load_address() == addr) //load_addr ?
120        {
121            return (&*sect);
122        }
123    }
124    return NULL;
125}
126
[238]127/////////////////////////////////////////////////
[163]128void MeMo::buildSoft(const std::string &filename)
129{
130    if(!m_ginit)
[173]131        throw exception::RunTimeError(std::string("Can't get generator! ") );
[163]132
133    m_generator->write(filename);
134
135}
136
[238]137/////////////////////////////////////////////////////////
[173]138size_t MeMo::load_bin(std::string filename, void* buffer)
139{
140
141#ifdef MOVER_DEBUG
[238]142    std::cout  << "Loading binary blob from file '" << filename << "'" << std::endl;
[173]143#endif
144
145    std::ifstream input(filename.c_str(), std::ios_base::binary|std::ios_base::in);
146
147    if ( ! input.good() )
148        throw exception::RunTimeError(std::string("Can't open the file: ") + filename);
149
150    input.seekg( 0, std::ifstream::end );
151    size_t size = input.tellg();
152    input.seekg( 0, std::ifstream::beg );
153
154    if ( !buffer )
155        throw exception::RunTimeError("Empty buffer!");
156
157    input.read( (char*)buffer, size );
158
159    input.close();
160
161    return size;
162}
163
164
165/* get the size of the content of the filename */
[165]166//////////////////////////////////////////
[173]167size_t MeMo::bin_size(std::string filename)
[163]168{
[173]169
[163]170#ifdef MOVER_DEBUG
[173]171    std::cout  << "Trying to get the size of the binary blob '" << filename << "'" << std::endl;
[163]172#endif
173
[173]174    std::ifstream input(filename.c_str(), std::ios_base::binary|std::ios_base::in);
[163]175
176    if ( ! input.good() )
[173]177        throw exception::RunTimeError(std::string("Can't open the file: ") + filename);
[163]178
179    input.seekg( 0, std::ifstream::end );
[173]180    size_t size = input.tellg();
[163]181    input.seekg( 0, std::ifstream::beg );
182
[173]183    input.close();
[163]184
[173]185    return size;
[163]186}
187
[173]188
[165]189/////////////
[163]190MeMo::~MeMo()
191{
192    //std::cout << "Deleted MeMo " << *this << std::endl;
[173]193    //std::free(m_data);//should be done by the elfpp driver since we passed the pointer
[163]194    std::map<std::string, elfpp::object*>::iterator l;
195    for(l = m_loaders.begin(); l != m_loaders.end(); l++)
196    {
197        delete (*l).second;
198    }
199    //delete m_generator;
200}
201
202
203/////////////////////////////////////////////////////////////////////////////
204// various mapping_info data structure access functions
205/////////////////////////////////////////////////////////////////////////////
[187]206mapping_cluster_t* MeMo::get_cluster_base( mapping_header_t* header )
207{
208    return   (mapping_cluster_t*) ((char*)header +
209                                  MAPPING_HEADER_SIZE);
210}
[163]211/////////////////////////////////////////////////////////////////////////////
212mapping_pseg_t* MeMo::get_pseg_base( mapping_header_t* header )
213{
214    return   (mapping_pseg_t*)    ((char*)header +
215                                  MAPPING_HEADER_SIZE +
216                                  MAPPING_CLUSTER_SIZE*header->clusters);
217}
218/////////////////////////////////////////////////////////////////////////////
219mapping_vspace_t* MeMo::get_vspace_base( mapping_header_t* header )
220{
221    return   (mapping_vspace_t*)  ((char*)header +
222                                  MAPPING_HEADER_SIZE +
223                                  MAPPING_CLUSTER_SIZE*header->clusters +
224                                  MAPPING_PSEG_SIZE*header->psegs);
225}
226/////////////////////////////////////////////////////////////////////////////
227mapping_vseg_t* MeMo::get_vseg_base( mapping_header_t* header )
228{
229    return   (mapping_vseg_t*)    ((char*)header +
230                                  MAPPING_HEADER_SIZE +
231                                  MAPPING_CLUSTER_SIZE*header->clusters +
232                                  MAPPING_PSEG_SIZE*header->psegs +
233                                  MAPPING_VSPACE_SIZE*header->vspaces);
234}
235/////////////////////////////////////////////////////////////////////////////
236mapping_vobj_t* MeMo::get_vobj_base( mapping_header_t* header )
237{
238    return   (mapping_vobj_t*)    ((char*)header +
239                                  MAPPING_HEADER_SIZE +
240                                  MAPPING_CLUSTER_SIZE*header->clusters +
241                                  MAPPING_PSEG_SIZE*header->psegs +
[165]242                                  MAPPING_VSPACE_SIZE*header->vspaces +
243                                  MAPPING_VSEG_SIZE*header->vsegs);
[163]244}
245
246/////////////////////////////////////////////////////////////////////////////
247// print the content of the mapping_info data structure
248////////////////////////////////////////////////////////////////////////
249void MeMo::print_mapping_info(void* desc)
250{
251    mapping_header_t*   header = (mapping_header_t*)desc; 
252
253    mapping_pseg_t*         pseg    = get_pseg_base( header );;
254    mapping_vspace_t*   vspace  = get_vspace_base ( header );;
255    mapping_vseg_t*         vseg    = get_vseg_base ( header );
256    mapping_vobj_t*         vobj    = get_vobj_base ( header );
257
258    // header
[165]259    std::cout << std::hex << "mapping_info" << std::dec << std::endl
[163]260              << " + signature = " << header->signature << std::endl
[215]261              << " + name      = " << (char*)header->name      << std::endl
[165]262              << " + clusters  = " << header->clusters  << std::endl
263              << " + psegs     = " << header->psegs     << std::endl
264              << " + vspaces   = " << header->vspaces   << std::endl
265              << " + globals   = " << header->globals   << std::endl
266              << " + vsegs     = " << header->vsegs     << std::endl
267              << " + vobjs     = " << header->vsegs     << std::endl
268              << " + tasks     = " << header->tasks     << std::endl;
[163]269
270    // psegs
271    for ( size_t pseg_id = 0 ; pseg_id < header->psegs ; pseg_id++ )
272    {
[165]273        std::cout << "pseg " << pseg[pseg_id].name << std::hex << std::endl
274                  << " + base   = " << pseg[pseg_id].base   << std::endl
275                  << " + length = " << pseg[pseg_id].length << std::endl ;
[163]276    }
277
278    // globals
279    for ( size_t vseg_id = 0 ; vseg_id < header->globals ; vseg_id++ )
280    {
[165]281        std::cout << std::endl;
[215]282        //std::cout << vseg[vseg_id].psegid << std::endl;
[165]283        std::cout << "global vseg "   << vseg[vseg_id].name << std::hex << std::endl
284                  << " + vbase    = " << vseg[vseg_id].vbase << std::endl
285                  << " + length   = " << vseg[vseg_id].length << std::endl
286                  << " + mode     = " << (size_t)vseg[vseg_id].mode << std::endl
287                  << " + ident    = " << (bool)vseg[vseg_id].ident << std::endl
288                  << " + psegname = " << pseg[vseg[vseg_id].psegid].name << std::endl;
289        for( size_t vobj_id = vseg[vseg_id].vobj_offset ; 
290                    vobj_id < vseg[vseg_id].vobj_offset + vseg[vseg_id].vobjs ; 
291                    vobj_id++ )
[163]292        {
[165]293            std::cout << "\t vobj "        << vobj[vobj_id].name    << std::endl
294                      << "\t + index   = " << std::dec << vobj_id   << std::endl
295                      << "\t + type    = " << vobj[vobj_id].type    << std::endl
296                      << "\t + length  = " << vobj[vobj_id].length  << std::endl
297                      << "\t + align   = " << vobj[vobj_id].align   << std::endl
298                      << "\t + binpath = " << vobj[vobj_id].binpath << std::endl
299                      << "\t + init    = " << vobj[vobj_id].init    << std::endl;
[163]300        }
301    }
302
303    // vspaces
304    for ( size_t vspace_id = 0 ; vspace_id < header->vspaces ; vspace_id++ )
305    {
[165]306        std::cout << "***vspace "  << vspace[vspace_id].name   << std::endl
307                  << " + vsegs = " <<  vspace[vspace_id].vsegs << std::endl
308                  << " + vobjs = " <<  vspace[vspace_id].vobjs << std::endl
309                  << " + tasks = " <<  vspace[vspace_id].tasks << std::endl;
[163]310
311        for ( size_t vseg_id = vspace[vspace_id].vseg_offset ; 
312              vseg_id < (vspace[vspace_id].vseg_offset + vspace[vspace_id].vsegs) ; 
313              vseg_id++ )
314        {
[165]315            std::cout << "\t vseg " << vseg[vseg_id].name  << std::endl
316                      << "\t + vbase    = " << vseg[vseg_id].vbase             << std::endl
317                      << "\t + length   = " << vseg[vseg_id].length            << std::endl
318                      << "\t + mode     = " << (size_t)vseg[vseg_id].mode      << std::endl
319                      << "\t + ident    = " << (bool)vseg[vseg_id].ident       << std::endl
320                      << "\t + psegname = " << pseg[vseg[vseg_id].psegid].name << std::endl;
321            for(size_t vobj_id = vseg[vseg_id].vobj_offset ; 
322                       vobj_id < vseg[vseg_id].vobj_offset + vseg[vseg_id].vobjs ; 
323                       vobj_id++ )
[163]324            {
[165]325                std::cout << "\t\t vobj "      << vobj[vobj_id].name      << std::endl
326                          << "\t\t + index   = " << std::dec << vobj_id   << std::endl
327                          << "\t\t + type    = " << vobj[vobj_id].type    << std::endl
328                          << "\t\t + length  = " << vobj[vobj_id].length  << std::endl
329                          << "\t\t + align   = " << vobj[vobj_id].align   << std::endl
330                          << "\t\t + binpath = " << vobj[vobj_id].binpath << std::endl
331                          << "\t\t + init    = " << vobj[vobj_id].init    << std::endl;
[163]332            }
333        }
334
335    }
336} // end print_mapping_info()
337
338
[165]339//////////////////////////////////////////
[173]340void MeMo::vseg_map( mapping_vseg_t* vseg)
[163]341{
[238]342    mapping_vobj_t*  vobj   = get_vobj_base( (mapping_header_t*) m_data );
343    PSeg             *ps = &(m_psegh.get(vseg->psegid));
344    elfpp::section*  sect = NULL;
345    size_t           cur_vaddr;
346    paddr_t          cur_paddr;
347    bool             first = true;
348    bool             aligned = false;
349    VSeg*            vSO = new VSeg;
350    mapping_vobj_t*  cur_vobj;
[163]351
352    vSO->m_name = std::string(vseg->name);
[238]353    vSO->m_vma  = vseg->vbase;
354    vSO->m_lma  = ps->nextLma();
[163]355
356    cur_vaddr = vseg->vbase;
357    cur_paddr = ps->nextLma();
358
359    size_t simple_size = 0; //for debug
[173]360
[163]361#ifdef MOVER_DEBUG
[238]362std::cout << "--------------- vseg_map "<< vseg->name <<" ------------------" << std::endl;
[163]363#endif
[173]364
[238]365    for ( size_t vobj_id = vseg->vobj_offset ; 
366          vobj_id < (vseg->vobj_offset + vseg->vobjs) ; vobj_id++ )
[163]367    {
368        cur_vobj = &vobj[vobj_id];
369
370#ifdef MOVER_DEBUG
[238]371std::cout << std::hex << "current vobj("<< vobj_id <<"): " << cur_vobj->name
372          << " (" <<cur_vobj->vaddr << ")"
373          << " size: "<< cur_vobj->length
374          << " type: " <<  cur_vobj->type << std::endl;
[163]375#endif
[173]376        if(cur_vobj->type == VOBJ_TYPE_BLOB)
[163]377        {
[173]378            size_t blob_size;
379            std::string filePath(m_pathHandler.getFullPath(std::string(cur_vobj->binpath)));
[163]380
381#ifdef MOVER_DEBUG
[238]382std::cout << std::hex << "Handling: " << filePath << " ..." << std::endl;
[163]383#endif
[173]384
[238]385            if(!filePath.compare(m_path))    // local blob: map_info
[163]386            {
387#ifdef MOVER_DEBUG
[238]388std::cout << "Found the vseg of the mapping info" << std::endl;
[163]389#endif
[173]390                blob_size = this->m_size;
391                assert((blob_size >0) and "MAPPING INFO file is empty !?");
[163]392            }
393            else
[173]394            {
[163]395#ifdef MOVER_DEBUG
[238]396std::cout << "Found an BLOB vseg" << std::endl;
[163]397#endif
[173]398                blob_size = bin_size(filePath);
399            }
[163]400
[238]401            // creating a new section
[173]402            sect = new elfpp::section(*m_generator, elfpp::SHT_PROGBITS);
[163]403
[173]404            sect->set_name(std::string(cur_vobj->name));
405            sect->set_flags(elfpp::SHF_ALLOC | elfpp::SHF_WRITE);
[238]406            sect->set_size(blob_size);  //do the malloc for the get_content fonction
[163]407
[173]408            assert(sect->get_content());//check allocation
[163]409
[173]410            if(!filePath.compare(m_path))    //local blob: map_info
411                //memcpy(sect->get_content(), m_data, sect->get_size());
412                /* this way the modification of the elf size are propageted to the giet */
413                sect->set_content(this->m_data);
414            else
415                load_bin(filePath, sect->get_content());
[163]416
[173]417
418            if(blob_size > cur_vobj->length)
419            {
420                std::cout << std::hex << "!!! Warning, specified blob type vobj ("<<
421                cur_vobj->name  <<") size is "<< cur_vobj->length << ", the actual size is "
422                << blob_size  << " !!!" <<std::endl;
423                assert(0 and "blob vobj length smaller than the actual content" );//???
[163]424            }
425
[173]426            cur_vobj->length = blob_size;//set the true size of this BLOB vobj
427
428            vSO->m_file = filePath;
429            vSO->m_loadable = true;
430        }
431        else if(cur_vobj->type == VOBJ_TYPE_ELF)
432        {
433            if(!first)
434                throw exception::RunTimeError(std::string("elf vobj type, must be placed first in a vseg"));
435
436            size_t elf_size;
437            std::string filePath(m_pathHandler.getFullPath(std::string(cur_vobj->binpath)));
438#ifdef MOVER_DEBUG
439            std::cout << "Handling: " << filePath << " ..." << std::endl;
440            std::cout << "Found an ELF vseg" << std::endl;
441#endif
442            if(m_loaders.count(filePath) == 0 )
443                m_loaders[filePath] = new elfpp::object(filePath);
444            elfpp::object* loader = m_loaders[filePath];//TODO:free!?
445
[212]446            sect =  new elfpp::section(*get_sect_by_addr(loader, cur_vaddr));//copy: for the case we replicate the code
447            if (!sect)
448            {
449                std::cerr << "No section found for " << cur_vobj->name << " at "<< cur_vaddr << std::endl;
450                exit(-1);
451            }
[173]452
453            sect->set_name(std::string(cur_vobj->name));
454
455
456            elf_size = sect->get_size();
457            assert((elf_size > 0) and "ELF section empty ?");
458
459            if(!m_ginit)
460            {
461                /** Initailising the header of the generator from the first binary,
462                ** we suppose that the header is the same for all the binarys **/
[238]463                m_generator->copy_info(*loader, 64);
[173]464                m_ginit=true;
465            }
466
[163]467            if(elf_size > cur_vobj->length)
[173]468            {
[163]469                std::cout << "Warning, specified elf type vobj ("<<
470                cur_vobj->name  <<") size is "<< cur_vobj->length << ", the actual size is "
471                << elf_size  << std::endl;
[173]472                //assert((elf_size < cur_vobj->length) and "elf vobj length smaller than the actual content" );//???
473                assert((0) and "elf vobj length smaller than the actual content" );//???
474            }
[163]475
476            cur_vobj->length = elf_size;//set the true size of this ELF vobj
477
478            vSO->m_file = filePath;
[173]479            vSO->m_loadable = true;
[163]480        }
481
482        //aligning the vobj->paddr if necessary
[173]483        //
[163]484        if(cur_vobj->align)
485        {
486            cur_paddr = PSeg::align(cur_paddr, cur_vobj->align);
487            aligned = true;
488        }
489
490        cur_vaddr += cur_vobj->length;
491        cur_paddr += cur_vobj->length;
492        simple_size += cur_vobj->length;
[173]493        first = false;
[163]494    }
495
496    assert((cur_vaddr >= vseg->vbase ));
497    assert((cur_paddr >= ps->nextLma() ));
498
499    vSO->m_length = (cur_paddr - ps->nextLma()); //pageAlign is done by the psegs
500
501#ifdef MOVER_DEBUG
502    if(aligned)
503    {
504        std::cout << "vseg base "<< std::hex << ps->nextLma()
505        <<(ps->nextLma()+simple_size)  <<" size " << std::dec << simple_size <<
[173]506        std::endl;
[163]507
508        std::cout << "vseg aligned to: base: " << std::hex << ps->nextLma()
509        <<" to "<< std::hex << ps->nextLma()+vSO->m_length<< " size " << std::dec <<
510        vSO->m_length << std::endl;
511    }
512#endif
513
[173]514    vSO->m_ident = vseg->ident;
515
[238]516    //set the lma for vseg that are not peripherals
517    if(ps->type() != PSEG_TYPE_PERI)
[210]518    {
[238]519        if ( vseg->ident != 0 )    ps->addIdent( *vSO );
520        else                       ps->add( *vSO );
[210]521    }
[163]522
[238]523    // increment NextLma if required
524    if(ps->type() == PSEG_TYPE_RAM)
525    {
526        ps->incNextLma( vSO->m_length );
527    }
528
529    if(!sect) return;
530
[163]531#ifdef MOVER_DEBUG
532    std::cout << "section: "<< *sect <<"\n seted to: " << (*vSO) << std::endl;
533#endif
534
535    sect->set_vaddr((*vSO).lma());
[212]536    m_generator->add_section(*sect);
[163]537
538} // end vseg_map()
[173]539 
[163]540
[165]541///////////////////////////////
[163]542void MeMo::buildMap(void* desc)
543{
544    mapping_header_t*   header = (mapping_header_t*)desc; 
545
[187]546    mapping_cluster_t*  cluster = get_cluster_base( header );     
[163]547    mapping_vspace_t*   vspace = get_vspace_base( header );     
548    mapping_pseg_t*     pseg   = get_pseg_base( header ); 
549    mapping_vseg_t*     vseg   = get_vseg_base( header );
550
551    // get the psegs
[165]552
[163]553#ifdef MOVER_DEBUG
554std::cout << "\n******* Storing Pseg information *********\n" << std::endl;
555#endif
[165]556
[187]557    for ( size_t cluster_id = 0 ; cluster_id < header->clusters ; cluster_id++ )
[163]558    {
[187]559        for ( size_t pseg_id = cluster[cluster_id].pseg_offset ;
560              pseg_id < cluster[cluster_id].pseg_offset + cluster[cluster_id].psegs ;
561              pseg_id++ )
562        {
563            std::string name(pseg[pseg_id].name);
[238]564            PSeg *ps = new PSeg( name, 
565                                 pseg[pseg_id].base, 
566                                 pseg[pseg_id].length, 
567                                 pseg[pseg_id].type );
[187]568            m_psegh.m_pSegs.push_back(*ps);
569        }
[163]570    }
571
572    // map global vsegs
[165]573
[163]574#ifdef MOVER_DEBUG
575std::cout << "\n******* mapping global vsegs *********\n" << std::endl;
576#endif
[165]577
[163]578    for ( size_t vseg_id = 0 ; vseg_id < header->globals ; vseg_id++ )
579    {
580        vseg_map( &vseg[vseg_id]);
581    }
582
[165]583    // loop on virtual spaces to map private vsegs
[163]584    for (size_t vspace_id = 0 ; vspace_id < header->vspaces ; vspace_id++ )
585    {
586
587#ifdef MOVER_DEBUG
588std::cout << "\n******* mapping all vsegs of " << vspace[vspace_id].name << " *********\n" << std::endl;
589#endif
590           
591        for ( size_t vseg_id = vspace[vspace_id].vseg_offset ; 
592              vseg_id < (vspace[vspace_id].vseg_offset + vspace[vspace_id].vsegs) ; 
593              vseg_id++ )
594        {
595            vseg_map( &vseg[vseg_id]); 
596        }
597    } 
598
599} // end buildMap()
600
601
602
603// Local Variables:
604// tab-width: 4
605// c-basic-offset: 4
606// c-file-offsets:((innamespace . 0)(inline-open . 0))
607// indent-tabs-mode: nil
608// End:
609
610// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4
611
Note: See TracBrowser for help on using the repository browser.