source: trunk/hal/tsar_mips32/core/hal_exception.c @ 625

Last change on this file since 625 was 625, checked in by alain, 20 months ago

Fix a bug in the vmm_remove_vseg() function: the physical pages
associated to an user DATA vseg were released to the kernel when
the target process descriptor was in the reference cluster.
This physical pages release should be done only when the page
forks counter value is zero.
All other modifications are cosmetic.

File size: 19.8 KB
Line 
1/*
2 * hal_exception.c - implementation of exception handler for TSAR-MIPS32.
3 *
4 * Author   Alain Greiner (2016, 2017)
5 *
6 * Copyright (c) UPMC Sorbonne Universites
7 *
8 * This file is part of ALMOS-MKH.
9 *
10 * ALMOS-MKH is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; version 2.0 of the License.
13 *
14 * ALMOS-MKH is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with ALMOS-MKH; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 */
23
24#include <hal_kernel_types.h>
25#include <hal_irqmask.h>
26#include <hal_special.h>
27#include <hal_exception.h>
28#include <thread.h>
29#include <printk.h>
30#include <chdev.h>
31#include <vmm.h>
32#include <errno.h>
33#include <scheduler.h>
34#include <core.h>
35#include <syscalls.h>
36#include <shared_syscalls.h>
37#include <remote_busylock.h>
38#include <hal_kentry.h>
39#include <hal_exception.h>
40
41//////////////////////////////////////////////////////////////////////////////////////////
42//  Extern global variables
43//////////////////////////////////////////////////////////////////////////////////////////
44
45extern   chdev_directory_t    chdev_dir;  // allocated in the kernel_init.c file.
46
47//////////////////////////////////////////////////////////////////////////////////////////
48// This enum defines the mask values for an MMU exception code reported by the mips32.
49//////////////////////////////////////////////////////////////////////////////////////////
50
51typedef enum
52{
53    MMU_WRITE_PT1_UNMAPPED        = 0x0001,
54    MMU_WRITE_PT2_UNMAPPED        = 0x0002,
55    MMU_WRITE_PRIVILEGE_VIOLATION = 0x0004,
56    MMU_WRITE_ACCESS_VIOLATION    = 0x0008,
57    MMU_WRITE_UNDEFINED_XTN       = 0x0020,
58    MMU_WRITE_PT1_ILLEGAL_ACCESS  = 0x0040,
59    MMU_WRITE_PT2_ILLEGAL_ACCESS  = 0x0080,
60    MMU_WRITE_DATA_ILLEGAL_ACCESS = 0x0100,
61
62    MMU_READ_PT1_UNMAPPED         = 0x1001,
63    MMU_READ_PT2_UNMAPPED         = 0x1002,
64    MMU_READ_PRIVILEGE_VIOLATION  = 0x1004,
65    MMU_READ_EXEC_VIOLATION       = 0x1010,
66    MMU_READ_UNDEFINED_XTN        = 0x1020,
67    MMU_READ_PT1_ILLEGAL_ACCESS   = 0x1040,
68    MMU_READ_PT2_ILLEGAL_ACCESS   = 0x1080,
69    MMU_READ_DATA_ILLEGAL_ACCESS  = 0x1100,
70}
71mmu_exception_subtype_t;
72
73//////////////////////////////////////////////////////////////////////////////////////////
74// This enum defines the relevant values for XCODE field in mips32 CP0_CR register.
75//////////////////////////////////////////////////////////////////////////////////////////
76
77typedef enum
78{
79    XCODE_ADEL = 0x4,        // Illegal address for data load
80    XCODE_ADES = 0x5,        // Illegal address for data store
81    XCODE_IBE  = 0x6,        // Instruction MMU exception       (can be NON-FATAL)
82    XCODE_DBE  = 0x7,        // Data MMU exception              (can be NON-FATAL)
83    XCODE_RI   = 0xA,        // Reserved instruction exception
84    XCODE_CPU  = 0xB,        // Coprocessor unusable exception  (can be NON-FATAl)
85    XCODE_OVR  = 0xC,        // Arithmetic Overflow exception
86}
87xcode_values_t;
88
89/////////////////////////////////////////////
90char * hal_mmu_exception_str( uint32_t code )
91{
92  switch (code) {
93    case (MMU_WRITE_PT1_UNMAPPED):        return "WRITE_PT1_UNMAPPED";
94    case (MMU_WRITE_PT2_UNMAPPED):        return "WRITE_PT2_UNMAPPED";
95    case (MMU_WRITE_PRIVILEGE_VIOLATION): return "WRITE_PRIVILEGE_VIOLATION";
96    case (MMU_WRITE_ACCESS_VIOLATION):    return "WRITE_ACCESS_VIOLATION";
97    case (MMU_WRITE_UNDEFINED_XTN):       return "WRITE_UNDEFINED_XTN";
98    case (MMU_WRITE_PT1_ILLEGAL_ACCESS):  return "WRITE_PT1_ILLEGAL_ACCESS";
99    case (MMU_WRITE_PT2_ILLEGAL_ACCESS):  return "WRITE_PT2_ILLEGAL_ACCESS";
100    case (MMU_WRITE_DATA_ILLEGAL_ACCESS): return "WRITE_DATA_ILLEGAL_ACCESS";
101    case (MMU_READ_PT1_UNMAPPED):         return "READ_PT1_UNMAPPED";
102    case (MMU_READ_PT2_UNMAPPED):         return "READ_PT2_UNMAPPED";
103    case (MMU_READ_PRIVILEGE_VIOLATION):  return "READ_PRIVILEGE_VIOLATION";
104    case (MMU_READ_EXEC_VIOLATION):       return "READ_EXEC_VIOLATION";
105    case (MMU_READ_UNDEFINED_XTN):        return "READ_UNDEFINED_XTN";
106    case (MMU_READ_PT1_ILLEGAL_ACCESS):   return "READ_PT1_ILLEGAL_ACCESS";
107    case (MMU_READ_PT2_ILLEGAL_ACCESS):   return "READ_PT2_ILLEGAL_ACCESS";
108    case (MMU_READ_DATA_ILLEGAL_ACCESS):  return "READ_DATA_ILLEGAL_ACCESS";
109    default:                              return "undefined";
110  }
111}
112
113//////////////////////////////////////////////////////////////////////////////////////////
114// This function is called when a FPU Coprocessor Unavailable exception has been
115// detected for the calling thread.
116// It enables the FPU, It saves the current FPU context in the current owner thread
117// descriptor if required, and restore the FPU context from the calling thread descriptor.
118//////////////////////////////////////////////////////////////////////////////////////////
119// @ this     : pointer on faulty thread descriptor.
120// @ return always EXCP_NON_FATAL
121//////////////////////////////////////////////////////////////////////////////////////////
122error_t hal_fpu_exception( thread_t * this )
123{
124        core_t   * core = this->core; 
125
126    // enable FPU (in core SR) 
127        hal_fpu_enable();
128
129    // save FPU register values in current owner thread if required
130        if( core->fpu_owner != NULL )
131    {
132        if( core->fpu_owner != this )
133            {
134            // save the FPU registers to current owner thread context
135                    hal_fpu_context_save( XPTR( local_cxy , core->fpu_owner ) );
136
137            // restore FPU registers from requesting thread context
138                hal_fpu_context_restore( this );
139
140            // attach the FPU to the requesting thread
141                core->fpu_owner = this;
142        }
143        }
144    else
145    {
146        // restore FPU registers from requesting thread context
147            hal_fpu_context_restore( this );
148
149        // attach the FPU to the requesting thread
150            core->fpu_owner = this;
151    }
152
153        return EXCP_NON_FATAL;
154
155}  // end hal_fpu_exception()
156
157//////////////////////////////////////////////////////////////////////////////////////////
158// This function is called when an MMU exception has been detected (IBE / DBE).
159// It get the relevant exception arguments from the MMU.
160// It signal a fatal error in case of illegal access. In case of page unmapped,
161// it get the client process to access the relevant VMM: for a RPC thread, the client
162// process is NOT the calling thread process.
163// Then, it checks that the faulty address belongs to a registered vseg, update the local
164// vseg list from the reference cluster if required, and signal a fatal user error
165// in case of illegal virtual address. Finally, it updates the local page table from the
166// reference cluster.
167//////////////////////////////////////////////////////////////////////////////////////////
168// @ this     : pointer on faulty thread descriptor.
169// @ excPC    :
170// @ is_ins   : IBE if true / DBE if false.
171// @ return EXCP_NON_FATAL / EXCP_USER_ERROR / EXCP_KERNEL_PANIC
172//////////////////////////////////////////////////////////////////////////////////////////
173error_t hal_mmu_exception( thread_t * this,
174                           uint32_t   excPC,
175                           bool_t     is_ins ) 
176{
177        process_t      * process;
178    error_t          error;
179
180    uint32_t         mmu_ins_excp_code;
181    uint32_t         mmu_ins_bad_vaddr;
182    uint32_t         mmu_dat_excp_code;
183    uint32_t         mmu_dat_bad_vaddr;
184
185    uint32_t         bad_vaddr;
186    uint32_t         excp_code;
187
188    // check thread type
189    if( CURRENT_THREAD->type != THREAD_USER )
190    {
191        printk("\n[PANIC] in %s : illegal thread type %s\n",
192        __FUNCTION__, thread_type_str(CURRENT_THREAD->type) );
193
194        return EXCP_KERNEL_PANIC;
195    }
196
197    // get faulty thread process 
198    process = this->process;
199
200    // get relevant values from MMU
201        hal_get_mmu_excp( &mmu_ins_excp_code,
202                          &mmu_ins_bad_vaddr,
203                          &mmu_dat_excp_code, 
204                          &mmu_dat_bad_vaddr );
205
206    // get exception code and faulty vaddr, depending on IBE/DBE
207    if( is_ins )
208    {
209        excp_code = mmu_ins_excp_code;
210        bad_vaddr = mmu_ins_bad_vaddr;
211    }
212    else 
213    {
214        excp_code = mmu_dat_excp_code;
215        bad_vaddr = mmu_dat_bad_vaddr;
216    }
217
218#if DEBUG_HAL_EXCEPTIONS
219uint32_t cycle = (uint32_t)hal_get_cycles();
220if( DEBUG_HAL_EXCEPTIONS < cycle )
221printk("\n[%s] thread[%x,%x] on core [%x,%x] enter\n  is_ins %d / %s / vaddr %x / cycle %d\n",
222__FUNCTION__, process->pid, this->trdid, local_cxy, this->core->lid,
223is_ins, hal_mmu_exception_str(excp_code), bad_vaddr, cycle);
224#endif
225
226   // analyse exception code
227    switch( excp_code )
228    {
229        case MMU_WRITE_PT1_UNMAPPED:      // can be non fatal
230        case MMU_WRITE_PT2_UNMAPPED:      // can be non fatal
231        case MMU_READ_PT1_UNMAPPED:       // can be non fatal
232        case MMU_READ_PT2_UNMAPPED:       // can be non fatal
233        {
234            // try to map the unmapped PTE
235            error = vmm_handle_page_fault( process, 
236                                           bad_vaddr >> CONFIG_PPM_PAGE_SHIFT );
237
238            if( error == EXCP_NON_FATAL )            // page-fault successfully handled
239            {
240
241#if DEBUG_HAL_EXCEPTIONS
242cycle = (uint32_t)hal_get_cycles();
243if( DEBUG_HAL_EXCEPTIONS < cycle )
244printk("\n[%s] thread[%x,%x] on core [%x,%x] exit\n  page-fault handled for vaddr = %x\n",
245__FUNCTION__, process->pid, this->trdid, local_cxy, this->core->lid, bad_vaddr );
246#endif
247 
248                return EXCP_NON_FATAL;
249            }
250            else if( error == EXCP_USER_ERROR )      // illegal vaddr
251            {
252                printk("\n[ERROR] in %s : thread[%x,%x] on core[%x,%x] / cycle %d\n"
253                "  %s : epc %x / badvaddr %x / is_ins %d\n",
254                __FUNCTION__, this->process->pid, this->trdid, local_cxy,
255                this->core->lid, (uint32_t)hal_get_cycles(),
256                hal_mmu_exception_str(excp_code), excPC, bad_vaddr, is_ins );
257
258                        return EXCP_USER_ERROR;
259            } 
260            else  // error == EXCP_KERNEL_PANIC 
261            {
262                printk("\n[PANIC] in %s : thread[%x,%x] on core[%x,%x] / cycle %d\n"
263                "  %s : epc %x / badvaddr %x / is_ins %d\n",
264                __FUNCTION__, this->process->pid, this->trdid, local_cxy,
265                this->core->lid, (uint32_t)hal_get_cycles(),
266                hal_mmu_exception_str(excp_code), excPC, bad_vaddr, is_ins );
267
268                        return EXCP_KERNEL_PANIC;
269            } 
270        }
271        case MMU_WRITE_PRIVILEGE_VIOLATION:  // illegal user error
272        case MMU_READ_PRIVILEGE_VIOLATION:   // illegal
273        {
274            printk("\n[ERROR] in %s : thread[%x,%x] on core[%x,%x] / cycle %d\n"
275            "  %s : epc %x / badvaddr %x / is_ins %d\n",
276            __FUNCTION__, this->process->pid, this->trdid, local_cxy,
277            this->core->lid, (uint32_t)hal_get_cycles(),
278            hal_mmu_exception_str(excp_code), excPC, bad_vaddr, is_ins );
279
280            return EXCP_USER_ERROR;
281        }
282        case MMU_WRITE_ACCESS_VIOLATION:    // can be non fatal if COW
283        {
284            // try to handle a possible COW
285            error = vmm_handle_cow( process,
286                                    bad_vaddr >> CONFIG_PPM_PAGE_SHIFT );
287
288            if( error == EXCP_NON_FATAL )        // COW successfully handled
289            {
290
291#if DEBUG_HAL_EXCEPTIONS
292cycle = (uint32_t)hal_get_cycles();
293if( DEBUG_HAL_EXCEPTIONS < cycle )
294printk("\n[%s] thread[%x,%x] exit / copy-on-write handled for vaddr = %x\n",
295__FUNCTION__, process->pid, this->trdid, bad_vaddr );
296#endif
297                return EXCP_NON_FATAL;
298            } 
299            else if( error == EXCP_USER_ERROR )  // illegal write access
300            {
301                    printk("\n[ERROR] in %s : thread[%x,%x] on core[%x,%x] / cycle %d\n"
302                    "  %s : epc %x / badvaddr %x / is_ins %d\n",
303                    __FUNCTION__, this->process->pid, this->trdid, local_cxy,
304                    this->core->lid, (uint32_t)hal_get_cycles(),
305                    hal_mmu_exception_str(excp_code), excPC, bad_vaddr, is_ins );
306
307                            return EXCP_USER_ERROR;
308            }
309            else   // error == EXCP_KERNEL_PANIC
310            {
311                printk("\n[PANIC] in %s : thread[%x,%x] on core[%x,%x] / cycle %d\n"
312                "  %s : epc %x / badvaddr %x / is_ins %d\n",
313                __FUNCTION__, this->process->pid, this->trdid, local_cxy,
314                this->core->lid, (uint32_t)hal_get_cycles(),
315                hal_mmu_exception_str(excp_code), excPC, bad_vaddr, is_ins );
316
317                        return EXCP_USER_ERROR;
318            }
319        }
320        case MMU_READ_EXEC_VIOLATION:        // user error
321        {
322            printk("\n[ERROR] in %s : thread[%x,%x] on core[%x,%x] / cycle %d\n"
323            "  %s : epc %x / badvaddr %x / is_ins %d\n",
324            __FUNCTION__, this->process->pid, this->trdid, local_cxy,
325            this->core->lid, (uint32_t)hal_get_cycles(),
326            hal_mmu_exception_str(excp_code), excPC, bad_vaddr, is_ins );
327
328            return EXCP_USER_ERROR;
329        }
330        default:                             // this is a kernel error   
331        {
332            printk("\n[PANIC] in %s : thread[%x,%x] on core[%x,%x] / cycle %d\n"
333            "  %s : epc %x / badvaddr %x / is_ins %d\n",
334            __FUNCTION__, this->process->pid, this->trdid, local_cxy,
335            this->core->lid, (uint32_t)hal_get_cycles(),
336            hal_mmu_exception_str(excp_code), excPC, bad_vaddr, is_ins );
337
338            return EXCP_KERNEL_PANIC;
339        }
340    } 
341} // end hal_mmu_exception()
342
343//////////////////////////////////////////////////////////////////////////////////////////
344// This function prints on the kernel terminal the saved context (core registers)
345// and the thread state of a faulty thread.
346//////////////////////////////////////////////////////////////////////////////////////////
347// @ this     : pointer on faulty thread descriptor.
348//////////////////////////////////////////////////////////////////////////////////////////
349static void hal_exception_dump( thread_t * this )
350{
351    core_t    * core    = this->core;
352    process_t * process = this->process;
353    reg_t     * uzone   = this->uzone_current;
354
355    // get pointers on TXT0 chdev
356    xptr_t    txt0_xp  = chdev_dir.txt_tx[0];
357    cxy_t     txt0_cxy = GET_CXY( txt0_xp );
358    chdev_t * txt0_ptr = GET_PTR( txt0_xp );
359
360    // get extended pointer on remote TXT0 chdev lock
361    xptr_t  lock_xp = XPTR( txt0_cxy , &txt0_ptr->wait_lock );
362
363    // get TXT0 lock in busy waiting mode
364    remote_busylock_acquire( lock_xp );
365
366    nolock_printk("\n=== thread(%x,%x) / core[%d] / cycle %d ===\n",
367    process->pid, this->trdid, core->lid, (uint32_t)hal_get_cycles() );
368
369        nolock_printk("busylocks = %d / blocked_vector = %X / flags = %X\n\n",
370    this->busylocks, this->blocked, this->flags );
371
372    nolock_printk("c0_cr   %X  c0_epc  %X  c0_sr  %X  c0_th  %X\n",
373    uzone[UZ_CR], uzone[UZ_EPC], uzone[UZ_SR], uzone[UZ_TH] );
374
375    nolock_printk("c2_mode %X  c2_ptpr %X\n",
376    uzone[UZ_MODE], uzone[UZ_PTPR] );
377
378    nolock_printk("at_01   %X  v0_2    %X  v1_3   %X  a0_4   %X  a1_5   %X\n",
379        uzone[UZ_AT], uzone[UZ_V0], uzone[UZ_V1], uzone[UZ_A0], uzone[UZ_A1] );
380
381    nolock_printk("a2_6    %X  a3_7    %X  t0_8   %X  t1_9   %X  t2_10  %X\n",
382        uzone[UZ_A2], uzone[UZ_A3], uzone[UZ_T0], uzone[UZ_T1], uzone[UZ_T2] );
383 
384    nolock_printk("t3_11   %X  t4_12   %X  t5_13  %X  t6_14  %X  t7_15  %X\n",
385        uzone[UZ_T3], uzone[UZ_T4], uzone[UZ_T5], uzone[UZ_T6], uzone[UZ_T7] );
386
387    nolock_printk("s0_16   %X  s1_17   %X  s2_18  %X  s3_19  %X  s4_20  %X\n",
388        uzone[UZ_S0], uzone[UZ_S1], uzone[UZ_S2], uzone[UZ_S3], uzone[UZ_S4] );
389 
390    nolock_printk("s5_21   %X  s6_22   %X  s7_23  %X  t8_24  %X  t9_25  %X\n",
391        uzone[UZ_S5], uzone[UZ_S6], uzone[UZ_S7], uzone[UZ_T8], uzone[UZ_T9] );
392
393    nolock_printk("gp_28   %X  sp_29   %X  S8_30  %X  ra_31  %X\n",
394        uzone[UZ_GP], uzone[UZ_SP], uzone[UZ_S8], uzone[UZ_RA] );
395
396    // release the lock
397    remote_busylock_release( lock_xp );
398
399}  // end hal_exception_dump()
400
401/////////////////////////////
402void hal_do_exception( void )
403{
404    uint32_t   * uzone;
405    thread_t   * this;
406        error_t      error;
407        uint32_t     excCode;                  // 4 bits XCODE from CP0_CR
408        uint32_t     excPC;                    // fauty instruction address
409
410    // get pointer on faulty thread uzone
411    this  = CURRENT_THREAD;
412    uzone = (uint32_t *)CURRENT_THREAD->uzone_current;
413
414    // get XCODE and EPC from UZONE
415        excCode        = (uzone[UZ_CR] >> 2) & 0xF;
416    excPC          = uzone[UZ_EPC];
417
418#if DEBUG_HAL_EXCEPTIONS
419uint32_t cycle = (uint32_t)hal_get_cycles();
420if( DEBUG_HAL_EXCEPTIONS < cycle )
421printk("\n[%s] thread[%x,%x] enter / core[%x,%d] / epc %x / xcode %x / cycle %d\n",
422__FUNCTION__, this->process->pid, this->trdid,
423local_cxy, this->core->lid, excPC, excCode, cycle );
424#endif
425
426        switch(excCode)
427        {
428        case XCODE_DBE:     // Data Bus Error : can be non fatal if page fault
429        {
430                    error = hal_mmu_exception( this , excPC , false );  // data MMU exception
431            break;
432        }
433            case XCODE_IBE:     // Instruction Bus Error : can be non fatal if page fault
434        {
435                    error = hal_mmu_exception( this , excPC , true );   // ins MMU exception
436                    break;
437        }
438            case XCODE_CPU:    // Coprocessor unavailable : can be non fatal if FPU
439        {
440            if( ((uzone[UZ_CR] >> 28) & 0x3) == 1 )             // FPU
441            {
442                error = hal_fpu_exception( this );
443            }
444            else                                                // undefined coprocessor
445            {
446                printk("\n[USER_ERROR] in %s for thread[%x,%x]\n"
447                "   undefined coprocessor / epc %x\n",
448                __FUNCTION__, this->process->pid, this->trdid, excPC );
449
450                        error = EXCP_USER_ERROR;
451            }
452                    break;
453        }
454        case XCODE_OVR:    // Arithmetic Overflow : user fatal error
455        {
456            printk("\n[USER_ERROR] in %s for thread[%x,%x]\n"
457            "   arithmetic overflow / epc %x\n",
458            __FUNCTION__, this->process->pid, this->trdid, excPC );
459
460                    error = EXCP_USER_ERROR;
461                break;
462        }
463        case XCODE_RI:     // Reserved Instruction : user fatal error
464        {
465            printk("\n[USER_ERROR] in %s for thread[%x,%x]\n"
466            "   reserved instruction / epc %x\n",
467            __FUNCTION__, this->process->pid, this->trdid, excPC );
468
469                    error = EXCP_USER_ERROR;
470                break;
471        }
472        case XCODE_ADEL:   // user fatal error
473        {
474            printk("\n[USER_ERROR] in %s for thread[%x,%x]\n"
475            "   illegal data load address / epc %x / bad_address %x\n",
476            __FUNCTION__, this->process->pid, this->trdid, excPC, hal_get_bad_vaddr() );
477
478                    error = EXCP_USER_ERROR;
479                break;
480        }
481        case XCODE_ADES:   //   user fatal error
482        {
483            printk("\n[USER_ERROR] in %s for thread[%x,%x]\n"
484            "   illegal data store address / epc %x / bad_address %x\n",
485            __FUNCTION__, this->process->pid, this->trdid, excPC, hal_get_bad_vaddr() );
486
487                    error = EXCP_USER_ERROR;
488                break;
489        }
490        default:
491        {
492                    error = EXCP_KERNEL_PANIC;
493        }
494        }
495   
496    // analyse error code
497        if( error == EXCP_USER_ERROR )          //  user error => kill user process
498        {
499        hal_exception_dump( this );
500
501        sys_exit( EXIT_FAILURE );
502        }
503    else if( error == EXCP_KERNEL_PANIC )   // kernel error => kernel panic
504    {
505        hal_exception_dump( this );
506
507        hal_core_sleep();
508    }
509
510#if DEBUG_HAL_EXCEPTIONS
511cycle = (uint32_t)hal_get_cycles();
512if( DEBUG_HAL_EXCEPTIONS < cycle )
513printk("\n[%s] thread[%x,%x] exit / core[%x,%d] / epc %x / xcode %x / cycle %d\n",
514__FUNCTION__, this->process->pid, this->trdid,
515local_cxy, this->core->lid, excPC, excCode, cycle );
516#endif
517
518}  // end hal_do_exception()
519
520
Note: See TracBrowser for help on using the repository browser.