PowerAda String Parameters and Returns

From OC Systems Wiki!
Revision as of 20:34, 22 March 2018 by Swn (talk | contribs)
Jump to: navigation, search

PowerAda String Parameters and Function Returns

Aprobe's APC language is translated to C, so strings within APC are nul-terminated C strings. But variables and parameters in the program you're probing are subject to the requirements and low-level implementation of that language. This article discusses how the AIX PowerAda compiler represents Ada character string values in subprogram calls and function returns

Logging PowerAda Strings

You can easily log PowerAda strings as slices in APC. The slices are fixed length. For example, use:

 probe ....
 {
    on_entry
    {
       log ($my_string_param[1..5]);
     }
 }
 

to log the first 5 characters of the PowerAda string. To have more control of logging and access see the following sections.

PowerAda String Representation

An Ada string is not nul-terminated, nor must it start at 0 or 1. Instead an Ada string value actually consists of 3 values { s, lb, ub } where

  • s is string, the sequence of 1-byte characters of length ub-lb+1
  • lb is the lower bound, S'First, the positive integer index of the first character of s.
  • ub is the upper bound, S'Last, the last character's index which could be 0 or even negative for an empty string.

A string value may be "bounded" or "unbounded":

  • bounded means lb, ub are known at compile time and never change, like "subtype Fixed_String_T is String(1..255);"
  • unbounded means lb and ub might change and so must accompany s in memory as part of the value, like "String".

If an Ada subprogram has a parameter declared of type String, PowerAda passes its bounds as separate parameters immediately afterward. For example, Put_Line(s) is really Put_Line(s, lb, ub).

Unconstrained Function Returns

A function which returns an unbounded string, such as "function image(i : integer) return string" presents a special challenge. A function supposedly returns only one value, which is the string itself. Yet it must also return the bounds of the string, and also keep track of the memory in which the string is stored.

So a PowerAda function that returns a string adds 5 parameters:

len      : out natural; -- the length in bytes of the returned value 
lb       : out positive; -- the lower bound ('First) of the returned string
ub       : out integer; -- the upper bound ('Last) of the returned string
ufc_base : in out private; -- a heap collection for strings bigger than 150 characters
hparam   : in string(1..150) -- a buffer to hold a return value that fits

The return value itself is the address of the first character of the string, whether it's in hparam or ufc_base. If the return value is not identical to the hparam value passed in, then it was allocated from the heap and is part of ufc_base. This ufc_base value passed back must be deallocated by the caller via a call to the PowerAda runtime but this is usually not of concern in a probe.

When compiling an APC file, the 'apc' command can access the PowerAda library to figure out the subtype of a string parameter or variable, whether it is bounded or unbounded, and where those bounds are stored. See String logging.

Discovering the Parameters and Return Values

Use apcgen to learn the names and order of all the parameters passed in and out of an Ada subprogram. For example, given this Ada function compiled into program pmain.exe:

>
package Parms is
...
  function Concatenate5 (S1, S2 : in String;
                         S3, S4, S5 : in String := "") return String;

Then
apcgen -qparam -qderef -p concatenate5 -x pmain.exe -o cat5.apc
will create file cat5.apc that contains the following:

// Automatically generated probe:
//    apcgen -qparam -qderef -p concatenate5 -x ./pmain.exe -o cat5.apc
#define MODULE_NAME ""
probe thread
{
   probe extern:"parms.concatenate5[1]" in MODULE_NAME
   {  // "concatenate5" declared in "parms.adb" at line 7
      on_entry
      {
         ap_LogSubprogramEntry;
         log parameter("\"s1\" (parameter) = ", $s1);
         log parameter("\"string_lb_1_2\" (parameter) = ", $string_lb_1_2);
         log parameter("\"string_ub_1_3\" (parameter) = ", $string_ub_1_3);
         log parameter("\"s2\" (parameter) = ", $s2);
         log parameter("\"string_lb_1_5\" (parameter) = ", $string_lb_1_5);
         log parameter("\"string_ub_1_6\" (parameter) = ", $string_ub_1_6);
         log parameter("\"s3\" (parameter) = ", $s3);
         log parameter("\"string_lb_1_8\" (parameter) = ", $string_lb_1_8);
         log parameter("\"string_ub_1_9\" (parameter) = ", $string_ub_1_9);
         log parameter("\"s4\" (parameter) = ", $s4);
         log parameter("\"string_lb_1_11\" (parameter) = ", $string_lb_1_11);
         log parameter("\"string_ub_1_12\" (parameter) = ", $string_ub_1_12);
         log parameter("\"s5\" (parameter) = ", $s5);
         log parameter("\"string_lb_1_14\" (parameter) = ", $string_lb_1_14);
         log parameter("\"string_ub_1_15\" (parameter) = ", $string_ub_1_15);
         log parameter("\"string_ufc_base_19\" (parameter) = ", $string_ufc_base_19);
         log parameter("\"string_hparam_20\" (parameter) = ", &$string_hparam_20);
      }
      on_exit
      {
         ap_LogSubprogramExit;
         // Only continue if normal exit.
         if (ap_ProbeActionReason != ap_ExitAction) return;
         log parameter("\"string_len_1_16\" (parameter) = ", $string_len_1_16);
         log parameter("\"string_lb_1_17\" (parameter) = ", $string_lb_1_17);
         log parameter("\"string_ub_1_18\" (parameter) = ", $string_ub_1_18);
         log parameter("\"string_ufc_base_19\" (parameter) = ", $string_ufc_base_19);
         log parameter("$return = ", (ap_AddressT)$$r3, " = ", $return);
      }
   }

}  // end probe thread 1

Note that the 5 parameters + 1 return value in the Ada declaration have expanded to become 5*3 + 5 parameters in the actual call.

Calling a PowerAda Subprogram From APC

This example illustrates how to call a PowerAda subprogram with lots of string parameters and a string function return.

-- parms.ads
package Parms is

  function Concatenate5 (S1, S2 : in String;
                         S3, S4, S5 : in String := "") return String;

end Parms;
-- parms.adb
package body Parms is

  function Concatenate5 (S1, S2 : in String;
                         S3, S4, S5 : in String := "") return String
  is
  begin
    return S1 & S2 & S3 & S4 & S5;
  end Concatenate5;

end Parms;
-- call.apc 
probe thread
{
  // the 4 out-mode parameters we will set on_exit from concatenate5 
  // and retrieve in the caller after the call.
  // These are C variables visible to all function probes 
  // within this thread probe.
  int cat5_string_len_1_16 = 0;
  int cat5_string_lb_1_17 = 0;
  int cat5_string_ub_1_18 = 0;
  char *cat5_string_ufc_base_19 = 0;


  // This probe was distilled down from the apcgen-generated one shown above.
  // Remember, names starting with '$' are variables within the application being probed
  // Names that don't start with '$' are C variables local to this APC file.
  probe extern:"parms.concatenate5[1]" 
  {
      on_exit
      {
         cat5_string_len_1_16    = $string_len_1_16;
         cat5_string_lb_1_17     = $string_lb_1_17;
         cat5_string_ub_1_18     = $string_ub_1_18;
         cat5_string_ufc_base_19 = $string_ufc_base_19;

      }
  }


  // A probe to call parms.concatenate5("one", "|two", "|three") and
  // copy the returned string into an aprobe-allocated C string.
  probe extern:"pmain.pmain"
  {
    on_line(first)
    {
      // the types used for PowerAda strings.  These are based
      // on the C code generated by APC.  Trust us.
      typedef typeof($("Standard.character")) Character_T;
      typedef Character_T String1_T[1];
      typedef String1_T *StringRef_T;

      // the caller of the function provides a buffer of size 150
      // for use when the returned string fits
      typedef char SmallReturn_T[150];
      static SmallReturn_T smallReturn;

      // adaResult holds the Ada string value returned from the call
      StringRef_T adaResult = NULL;

      // myResult holds a nul-terminated C string we'll copy adaResult to.
      ap_NameT      myResult;

       // Initialize thread-global variables to
       // make sure we're getting current values
       cat5_string_len_1_16 = 0;
       cat5_string_lb_1_17 = 0;
       cat5_string_ub_1_18 = 0;
       cat5_string_ufc_base_19 = 0;

       // In order to get out parameter values we have to probe the 
       // Ada function we're calling from within a probe.  This is
       // disabled by default, so allow it:
       ap_SetMaxProbeNestingLevel(1);

       // PowerAda relies on a special register which we must guarantee
       // is in place using the 'ap_BeforePoweradaCall' macro. 
       ap_BeforePoweradaCall;

       // adaResult = concatenate5("one","|two", "|three")
       adaResult = $("parms.concatenate5")
         ((StringRef_T)"one", 1, 3,
          (StringRef_T)"|two", 1, 4,
          (StringRef_T)"|three", 1, 6,
          (StringRef_T)"", 1, 0,
          (StringRef_T)"", 1, 0,
          0, 0, 0, 0, /* out params 16, 17, 18,  19 */
          smallReturn);

       // restore local registers after PowerAda call
       ap_AfterPoweradaCall;

       // disable probe nesting again.
       ap_SetMaxProbeNestingLevel(0);

       // copy the characters from the return value
       myResult = ap_Malloc(cat5_string_len_1_16 + 1); /* +1 for nul-terminator */
       strncpy(myResult, adaResult, cat5_string_len_1_16);

       // print the C copy of the result.  Could 'log' it, or save it or whatever.
       printf("myResult(%d .. %d) = '%s'\n",
              cat5_string_lb_1_17, cat5_string_ub_1_18, myResult);

       // free the local copy
       ap_Free(myResult);
    }
  }
}

Yes, this is a complicated example and could use more explanation, but it's a start.