ABAP Callstack auswerten – Wer ruft mich an?

“Unter einem Aufrufstapel (englischcall stack, procedure stack) versteht man in der Softwaretechnik und Informatik einen besonders genutzten Stapelspeicher, der zur Laufzeit eines Programms den Zustand der gerade aufgerufenen Unterprogramme enthält. ” (Wikipedia)

Manchmal ist es wichtig zu wissen, aus welchem Kontext ein Methodenaufruf kam. Solange man selber den Methodenaufruf macht, kann man diese Information natürlich in entsprechende Parameter packen. Aber wenn es vom System aufgerufene Methoden sind, wie z.B. bei BAdIs, dann hat man darauf keinen Einfluss. In meinem konkreten Beispiel geht es darum zu unterscheiden, ob ein Customer Exit für eine BW Bex Variable entweder

  1. Beim normalen Ausführen eines Reports oder
  2. Beim Simulieren für einen anderen Benutzer (Transaktion RSUDO/RSECADMIN)
  3. Im Hintergrund bei der Generierung der Berechtigungen

aufgerufen wird. In die Schnittstelle des Exits bekomme ich die notwendige Information nicht mit rein. Ein einfacher Workaround, um sich diese Information zu beschaffen, ist der Funktionsbaustein SYSTEM_CALLSTACK. Er liefert den vollständigen Callstack. Das folgende Listing zeigt einen einfachen Test dieser Funktion:

REPORT zjb_test.

CLASS lcl_outer DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS test_outer.
ENDCLASS.

CLASS lcl_inner DEFINITION.
  PUBLIC SECTION.
    CLASS-METHODS test_inner.
ENDCLASS.

lcl_outer=>test_outer( ).

CLASS lcl_inner IMPLEMENTATION.

  METHOD test_inner.
    DATA lt_callstack1  TYPE abap_callstack  .

    CALL FUNCTION 'SYSTEM_CALLSTACK'
      EXPORTING
        max_level    = 0
      IMPORTING
        callstack    = lt_callstack1.
    BREAK-POINT.
  ENDMETHOD.

ENDCLASS.

CLASS lcl_outer IMPLEMENTATION.

  METHOD test_outer.
    lcl_inner=>test_inner( ).
  ENDMETHOD.

ENDCLASS.

Ergebnis des Reports

Row  MAINPROGRAM  INCLUDE   LINE  BLOCKTYPE  BLOCKNAME           FLAG_SYSTEM<br>    ============================================================================
1    ZJB_TEST     ZJB_TEST  26    METHOD     TEST_INNER          
2    ZJB_TEST     ZJB_TEST  41    METHOD     TEST_OUTER          
3    ZJB_TEST     ZJB_TEST  18    EVENT      START-OF-SELECTION  

Sie sehen, dass hier die drei Ebenen in der Tabelle stehen.

Für das eigentliche Problem muss ich jetzt lediglich den Callstack durchsuchen, ob dort eine der Klassen vorkommt. In meinem Beispiel wäre z.B. die Suche nach der Klasse CL_RS2HANA_AUTH_MANAGER, Methode RUN_REPLICATION für die Erkennung der Situation 3 geeignet. Dazu gehört der folgende Callstack:

Row  MAINPROGRAM                       INCLUDE                              LINE  BLOCKTYPE     BLOCKNAME                                    FLAG_SYSTEM
========================================================================================================================================================
1    ZCL_BI_CALLSTACK==============CP  ZCL_BI_CALLSTACK==============CM001  5     METHOD        CHECK_CONTAINS_CLASSNAME                     
2    ZCL_BI_VARIABLE_AUTH==========CP  ZCL_BI_VARIABLE_AUTH==========CM001  9     METHOD        GET_RANGE_FOR_VARNAME                        
3    ZCL_BI_VAR_BI_MGMTID==========CP  ZCL_BI_VAR_BI_MGMTID==========CM001  10    METHOD        GET_RANGE_FOR_VNAM                           
4    ZCL_BI_VAR_ROOT===============CP  ZCL_BI_VAR_ROOT===============CM002  18    METHOD        IF_RSROA_VARIABLES_EXIT_BADI~PROCESS         
5    SAPLRRS0                          LRRS0U01                             42    FUNCTION      RRS_VAR_EXIT                                 
6    SAPLRSEC_CHECKS                   LRSEC_CHECKSF02                      107   FORM          GET_VALUE_FROM_CUST_EXIT                     
7    SAPLRSEC_CHECKS                   LRSEC_CHECKSF03                      1936  FORM          GET_LEAVES_AUTHORIZED                        
8    SAPLRSEC_CHECKS                   LRSEC_CHECKSF03                      1782  FORM          GET_VALUES_OF_LEAVES                         
9    SAPLRSEC_UTILS                    LRSEC_UTILSU02                       341   FUNCTION      RSEC_GET_AUTHS_FILTERED                      
10   CL_RS2HANA_AUTH_AUTHORIZATION=CP  CL_RS2HANA_AUTH_AUTHORIZATION=CCIMP  1224  METHOD        READ_AUTH_INFOPROVIDER                       
11   CL_RS2HANA_AUTH_AUTHORIZATION=CP  CL_RS2HANA_AUTH_AUTHORIZATION=CCIMP  744   METHOD        CONSTRUCTOR                                  
12   CL_RS2HANA_AUTH_AUTHORIZATION=CP  CL_RS2HANA_AUTH_AUTHORIZATION=CCIMP  1333  METHOD        GET                                          
13   CL_RS2HANA_AUTH_AUTHORIZATION=CP  CL_RS2HANA_AUTH_AUTHORIZATION=CM00A  20    METHOD        IF_RS2HANA_AUTH_AUTHORIZATION~IS_AUTHORIZED  
14   CL_RS2HANA_AUTH_MANAGER=======CP  CL_RS2HANA_AUTH_MANAGER=======CM02G  22    METHOD        _STEP_01C_CHECK_AUTHORIZATIONS               
15   CL_RS2HANA_AUTH_MANAGER=======CP  CL_RS2HANA_AUTH_MANAGER=======CM023  20    METHOD        RUN_STEP_01_PREPARE                          
16   CL_RS2HANA_AUTH_MANAGER=======CP  CL_RS2HANA_AUTH_MANAGER=======CM02D  27    METHOD        _RUN_REPLICATION                             
17   CL_RS2HANA_AUTH_MANAGER=======CP  CL_RS2HANA_AUTH_MANAGER=======CM022  125   METHOD        RUN_REPLICATION                              
18   RS2HANA_AUTH_RUN                  RS2HANA_AUTH_RUN_CL                  453   METHOD        RUN                                          
19   RS2HANA_AUTH_RUN                  RS2HANA_AUTH_RUN                     252   FORM          %_SEL_SCREEN                                 
20   RS2HANA_AUTH_RUN                  RS2HANA_AUTH_RUN                     483   MODULE (PAI)  %_END_OF_SCREEN                              X
21   RS2HANA_AUTH_RUN                  <SYSINI>                             18    EVENT         SYSTEM-EXIT                                  X

Für diese Situation habe ich eine Klasse geschrieben, die prüft, ob im Callstack eine Kombination aus Klasse und Methode vorkommt. Falls das nicht der Fall ist, wird eine Exception ausgelöst. Diese kann der Aufrufer dann abfangen und entsprechend handeln.

CLASS zcl_callstack DEFINITION
  PUBLIC FINAL CREATE PUBLIC .

  PUBLIC SECTION.
    CLASS-METHODS check_contains IMPORTING iv_classname  TYPE seoclsname
                                           iv_methodname TYPE seocmpname
                                 RAISING   zcx_not_found.

ENDCLASS.

CLASS zcl_callstack IMPLEMENTATION.
  METHOD check_contains.
    DATA lt_callstack  TYPE abap_callstack  .
    DATA lv_mainprogram_pattern TYPE progname VALUE '==============================CP'.
    DATA(lv_classlength) =  strlen( iv_classname ).

    lv_mainprogram_pattern(lv_classlength) = iv_classname.

    CALL FUNCTION 'SYSTEM_CALLSTACK'
      EXPORTING
        max_level    = 0
      IMPORTING
        et_callstack = lt_callstack.

    READ TABLE lt_callstack TRANSPORTING NO FIELDS
                            WITH KEY  mainprogram = lv_mainprogram_pattern
                                      blockname = iv_methodname.
    IF sy-subrc NE 0.
      RAISE EXCEPTION TYPE zcx_not_found.
    ENDIF.
    BREAK-POINT.
  ENDMETHOD.

ENDCLASS.

Das gezeigte Vorgehen funktioniert gut. Es sollte aber nur dann eingesetzt werden, wenn eine Weitergabe der Information auf andere Art, z.B. per Parameter, nicht möglich ist. Der Grund hierfür ist, das eine bestimmte Aufrufreihenfolge im Callstack keine definiert Schnittstelle darstellt. Intern kann die SAP alles ändern, solange der Exit mit den gleichen Parametern aufgerufen wird.

Schreib einen Kommentar