Set Up PIL Connectivity by Using Target Framework - MATLAB & Simulink (original) (raw)
Before you can run processor-in-the-loop (PIL) simulations, you must set up connectivity between Simulink® and your target hardware. The connectivity enables the PIL simulation to:
- Build the target application.
- Download, start, and stop the application on the target hardware.
- Support communication between Simulink and the target hardware.
Note
For Polyspace® Test™ C/C++ testing on target hardware, the same steps are required to set up a connection between Polyspace Test and the target hardware.
Use one of these workflows.
Workflow | When to use |
---|---|
target namespace withrtiostream API | Use this workflow if your target application has large I/O requirements that need high-bandwidth communication. See Use rtiostream API for PIL Target Connectivity.You can specify a debugger. See Support PIL Debugging andDebugExecutionTool Template. |
target namespace with debugger | Use this workflow if: You want a single debugger integration to provide PIL connectivity for target hardware systems that are supported by the debugger. This workflow requires little or no target-specific code.The I/O requirements of the target application are small. For example, unit tests on a component. If you use this workflow for a target application that has large I/O requirements, the execution of the application is slower.See Use Debugger for PIL Target Connectivity andDebugIOTool Template. |
Use rtiostream
API for PIL Target Connectivity
To provide PIL connectivity between Simulink and the target hardware, you can use the target namespace with the rtiostream API. In this example, your development computer is the target hardware.
Create the board description
Create atarget.Board
object that provides MATLAB® with a description of your target hardware.
hostTarget = target.create('Board', ...
'Name', 'Example Intel Board');
To support code generation and data I/O in PIL simulations, associate the object with processor, connection, and communication descriptions.
2. Associate the board with a processor
To support code generation, associate the board with atarget.Processor
object that contains a language implementation. For this example, create atarget.Processor
object and reuse an existingtarget.LanguageImplementation
object. For information about setting up a customtarget.LanguageImplementation
object, see Register New Hardware Devices.
processor = target.create('Processor', 'Name', 'ExampleProcessor');
processor.LanguageImplementations = target.get('LanguageImplementation', ...
'Intel-x86-64 (Windows64)');
Associate the target.Board
object with the newtarget.Processor
object by using theProcessors
property
hostTarget.Processors = processor;
3. Specify execution information for the target hardware
Create an object that contains details about executing the target application. The object describes the tool that is required to run the target application on the target hardware. To capture system commands for starting and stopping the target application, you can use theHostProcessExecutionTool
orSystemCommandExecutionTool
class.
applicationExecution = target.create('HostProcessExecutionTool');
applicationExecution.Name = 'Windows Application';
Create a Command
object for downloading and running the target application. Assign the string variable'$(EXE)'
to the String
property as a place holder for the target application name, which is not known until execution.
runCommand = target.create('Command');
runCommand.String = '$(EXE)';
applicationExecution.StartCommand = runCommand;
hostTarget.Tools.ExecutionTools = applicationExecution;
4. Create the communication interface for the target hardware
Create a CommunicationInterface
object that provides the target hardware with details of the communication channel and the rtiostream API implementation.
Use:
- The shipped TCP/IP
rtiostream
implementation source file. - A
BuildDependencies
object to specify, for thertiostream
API, the source files that are compiled with the target application. - A
MainFunction
object to pass arguments to the target application
comms = target.create('CommunicationInterface');
comms.Name = 'Windows TCP Interface';
comms.Channel = 'TCPChannel';
comms.APIImplementations = target.create('APIImplementation', ...
'Name', 'x86 RTIOStream Implementation');
comms.APIImplementations.API = target.create('API', 'Name', 'RTIO Stream');
comms.APIImplementations.BuildDependencies = target.create('BuildDependencies');
comms.APIImplementations.BuildDependencies.SourceFiles = ...
{fullfile('$(MATLAB_ROOT)', ...
'toolbox', ...
'coder', ...
'rtiostream', ...
'src', ...
'rtiostreamtcpip', ...
'rtiostream_tcpip.c')};
comms.APIImplementations.MainFunction = target.create('MainFunction', ...
'Name', 'TCP RtIOStream Main');
comms.APIImplementations.MainFunction.Arguments = {'-blocking', '1', '-port', '0'};
hostTarget.CommunicationInterfaces = comms;
5. Specify PIL protocol information
This step is optional. A PIL simulation uses a communication protocol for the transfer of data between Simulink and the target hardware. The target.PILProtocol class describes the parameters of the protocol. For this example, you can improve target run-time performance by increasing the I/O buffer size that the protocol uses.
Create a target.PILProtocol
object and specify I/O buffer size.
pilProtocol = target.create('PILProtocol');
pilProtocol.Name = 'Windows PIL Protocol';
pilProtocol.SendBufferSize = 50000;
pilProtocol.ReceiveBufferSize = 50000;
hostTarget.CommunicationProtocolStacks = pilProtocol;
If you do not perform this step, defaulttarget.PILProtocol
values are used.
6. Add profiling timer
You can configure a PIL simulation to produce execution-time profiles for the generated code. To support code execution profiling, you must create a timer object that describes the retrieval of current time from generated code running on the target hardware. The timer object description must include a description of the function that retrieves time and its implementation.
This example uses a C function, timestamp_x86
, which returns the current time as a uint64
data type.
timerSignature = target.create('Function');
timerSignature.Name = 'timestamp_x86';
timerSignature.ReturnType = 'uint64';
Capture the function in an API object.
timerApi = target.create('API');
timerApi.Functions = timerSignature;
timerApi.Language = target.Language.C;
timerApi.Name = 'Windows Timer API';
Capture the dependencies of the function, that is, the source and header files that are required to run the function.
timerDependencies = target.create('BuildDependencies');
timerDependencies.IncludeFiles = {'host_timer_x86.h'};
timerDependencies.IncludePaths = {'$(MATLAB_ROOT)/toolbox/coder/profile/src'};
timerDependencies.SourceFiles = {'host_timer_x86.c'};
Create an object that combines the API and dependencies.
timerImplementation = target.create('APIImplementation');
timerImplementation.API = timerApi;
timerImplementation.BuildDependencies = timerDependencies;
timerImplementation.Name = 'Windows Timer Implementation';
Create the timer object and associate it with the timer information.
timer = target.create('Timer');
timer.APIImplementation = timerImplementation;
timer.Name = 'Windows Timer';
Assign the timer to the processor object.
processor.Timers = timer;
7. Specify connection between development computer and target hardware
The previous steps created target hardware support for communications and running the target application. Now, set up a connection between your development computer and the target hardware by creating aTargetConnection
object. Specify:
- The communication channel, which is the same channel specified in the target hardware communication interface — see step 4.
- The connection properties.
- The target, which is the board description specified in previous steps.
connection = target.create('TargetConnection');
connection.Name = 'Host Process Connection';
connection.Target = hostTarget;
connection.CommunicationChannel = target.create('TCPChannel');
connection.CommunicationChannel.Name = 'External Process TCPCommunicationChannel';
connection.CommunicationChannel.IPAddress = 'localhost';
connection.CommunicationChannel.Port = '0';
Make board and connection objects persist across MATLAB sessions
To register the connectivity in MATLAB, add the target hardware and connection information to an internal database by using the target.add function. By default, the information is only available for the current MATLAB session. To make the registration persist across MATLAB sessions, specify the name-value pair'UserInstall', true
.
target.add([hostTarget connection], 'UserInstall', true);Specify board for model
You can now specify your development computer as the target hardware for PIL simulations. Before you run a PIL simulation, in the Configuration Parameters dialog box, set Hardware board toExample Intel Board
.
Support PIL Debugging
The workflow in Use rtiostream API for PIL Target Connectivity provides PIL connectivity between Simulink and the target hardware. To also support PIL debugging, implement the following steps after or instead of step 3 (Specify execution information for the target hardware):
Describe the debugger execution service implementation
Create atarget.ExecutionService
object that references the debugger abstraction interface.
debugExecutionTool = target.create('ExecutionService', ...
'Name', 'GDB Launch');
debugExecutionTool.APIImplementation = ...
target.create('APIImplementation', ...
'Name', 'myServiceImplementation');
debugExecutionTool.APIImplementation.BuildDependencies = ...
target.create('MATLABDependencies');
debugExecutionTool.APIImplementation.BuildDependencies.Classes = ...
{'myImplementationClass'};
debugExecutionTool.APIImplementation.API = ...
target.get('API', ...
'DebugExecutionTool');
2. Associate the debugger execution service with the board
Associate the target.ExecutionService
object with the target.Board
object.
hostTarget.Tools.DebugTools = debugExecutionTool;
DebugExecutionTool
Template
This section provides a pseudocode example oftarget.DebugExecutionTool
debugger abstraction. Use atarget.DebugExecutionTool
object to:
- Control the lifetime of an application running in the debugger.
- Automate applying breakpoints.
For interactions with the debugger, you can use MATLAB external language interfaces to APIs, which the debugger vendor supplies.
Section Descriptions for target.DebugExecutionTool Debugger Abstraction Example
target.DebugExecutionTool Object | Comments |
---|---|
classdef DebugExecutionToolTemplate < target.DebugExecutionTool | Example implementation oftarget.DebugExecutionTool debugger abstraction. |
properties (Access=private) DebuggerHandle; end | Optional. Add any state required to interact with the debugger or application being run in the debugger as a property of DebugExecutionTool. Apply private access so that only this file can access the state. For example, the state can be a handle to the debugger application provided by the debugger external API. |
methods | Method implementations that perform debugger interactions. Each method returns a'errFlag' logical flag. If the method executes, 'errFlag' is set tofalse. Consider also using the MATLAB error method to report custom error information. |
function this = DebugExecutionToolTemplate() end | Optional.Constructor for thetarget.DebugExecutionTool. The constructor creates an object of the tool allowing its methods to be called from that object. Perform basic initialization here. For example, setting state for calls to open. |
function errFlag = open(this) this.DebuggerHandle = this.callExternalDebugger('open'); errFlag = false; end | Optional.Add logic here to open the debugger. Typically, use system commands or the external API provided by the debugger.If you cannot open the debugger without also loading the application into the debugger, do not implement this method. Use the'loadApplication' method to implement the logic.If you cannot open the debugger without also starting execution of the application, do not implement this method. Use the'startApplication' method to implement the logic. |
function errFlag = loadApplication(this) this.callExternalDebugger('load application', ... this.Application, .... this.ApplicationCommandLineArguments); this.callExternalDebugger('set breakpoints', this.Breakpoints); errFlag = false; end | Optional.Load the application into the debugger.Add logic here to load the application into the debugger. For example, load a project into an IDE debugger or load the application executable file into the debugger harness.Also add logic to set breakpoints in the debugger to break execution.The path to the application file is stored in the Application property of this class which can you can access by usingthis.Application.Additional command line arguments for the application are stored inthis.ApplicationCommandLineArguments.Breakpoints to set are stored inthis.Breakpoints.If you cannot load the application without also starting execution of the application, do not implement this method. Implement the logic in the 'startApplication' method. |
function errFlag = startApplication(this) this.callExternalDebugger('start application'); errFlag = false; end | Required.Start the application execution in the debugger.Add logic here to start the application execution in the debugger.The path to the application to load is stored in theApplication property of this class, which you can access by usingthis.Application.Additional command line arguments to pass to the application are stored inthis.ApplicationCommandLineArguments.Breakpoints to set are stored inthis.Breakpoints. |
function errFlag = stopApplication(this) this.callExternalDebugger('stop application'); errFlag = false; end | Optional.Stop the application execution in the debugger.Add logic here to stop the application execution in the debugger.If your'startApplication' method opened the debugger, perform the opposite action here, i.e. close the debugger.If your'startApplication' method loaded the application, perform the opposite action here, i.e. unload the application from the debugger. |
function errFlag = unloadApplication(this) this.callExternalDebugger('unload application'); errFlag = false; end | Optional.Unloads the application from the debugger.If you implemented the'loadApplication' method, then add logic here to unload the application from the debugger.If your'loadApplication' method opened the debugger, perform the opposite action here, i.e. close the debugger. |
function errFlag = close(this) this.callExternalDebugger('close'); errFlag = false; end | Optional.Closes the debugger.Add logic here to close the debugger. If you implemented the 'open' method, you must also implement the 'close' method. |
end | end statement formethods. |
methods (Access=private) function output = callExternalDebugger(~, varargin) output = []; fprintf("\nInstructed the debugger to '%s'\n", varargin{1}); if nargin > 2 disp('Using arguments:'); disp(varargin(2:end)); end end end | Private methods. |
end | end statement forclassdef. |
Pseudocode for target.DebugExecutionTool Debugger Abstraction Example
classdef DebugExecutionToolTemplate < target.DebugExecutionTool % DEBUGEXECUTIONTOOLTEMPLATE Example implementation of target.DebugExecutionTool % debugger abstraction.
% Copyright 2023 The MathWorks, Inc.
properties (Access=private)
% Optional
% Add any state required to interact with the debugger or
% application being run in the debugger as a property of
% DebugExecutionTool. Apply private access so that only this file
% can access the state.
%
% For example, the state could be a handle to the debugger
% application provided by the debugger external API.
DebuggerHandle;
end
% Method implementations that perform debugger interactions.
% Each method will return a 'errFlag' logical flag. If the method
% executes, 'errFlag' is set to false.
% Consider also using the MATLAB error method to report custom
% error information.
methods
% Constructor for the target.DebugExecutionToolTemplate. The constructor
% creates an instance of the tool allowing its methods to be called
% on that instance. Optional.
function this = DebugExecutionToolTemplate()
% Basic initialization can be performed here. For example
% setting state ready for calls to open
end
% Open the debugger. Optional.
function errFlag = open(this)
% Add logic here to open the debugger. Typically, use system
% commands or the external API provided by the debugger.
%
% If you cannot open the debugger without also loading the
% application into the debugger, this method should not be
% implemented and the logic should be performed in the
% 'loadApplication' method.
%
% If you cannot open the debugger without also starting
% execution of the application, this method should not be
% implemented and the logic should be performed in the
% 'startApplication' method.
this.DebuggerHandle = this.callExternalDebugger('open');
errFlag = false;
end
% Load the application into the debugger. Optional.
function errFlag = loadApplication(this)
% Add logic here to load the application into the debugger.
% This could be loading a project into an IDE debugger or
% loading the application executable into the debugger harness.
%
% Additionally add logic to set breakpoints in the debugger to
% break execution.
%
% The path to the application to load is stored in the
% Application property of this class which can be accessed
% using this.Application in this method.
%
% Additional command line arguments to pass to the
% application are stored in
% this.ApplicationCommandLineArguments.
%
% Breakpoints to set are stored in this.Breakpoints
%
% If you cannot load the application without also starting
% execution of the application, this method should not be
% implemented and the logic should be performed in the
% 'startApplication' method.
this.callExternalDebugger('load application', ...
this.Application, ....
this.ApplicationCommandLineArguments);
this.callExternalDebugger('set breakpoints', this.Breakpoints);
errFlag = false;
end
% Start the application execution in the debugger. Required.
function errFlag = startApplication(this)
% Add logic here to start the application execution in the
% debugger.
%
% The path to the application to load is stored in the
% Application property of this class which can be accessed
% using this.Application in this method.
%
% Additional command line arguments to pass to the
% application are stored in
% this.ApplicationCommandLineArguments.
%
% Breakpoints to set are stored in this.Breakpoints
this.callExternalDebugger('start application');
errFlag = false;
end
% Stop the application execution in the debugger. Optional.
function errFlag = stopApplication(this)
% Add logic here to stop the application execution in the
% debugger.
% If your 'startApplication' method opened the debugger,
% perform the opposite action here, i.e. close the
% debugger.
%
% If your 'startApplication' method loaded the application,
% perform the opposite action here, i.e. unload the
% application from the debugger.
this.callExternalDebugger('stop application');
errFlag = false;
end
% Unloads the application from the debugger. Optional.
function errFlag = unloadApplication(this)
% If you implemented the 'loadApplication' method,
% then add logic here to unload the application from
% the debugger.
% If your 'loadApplication' method opened the debugger,
% perform the opposite action here, i.e. close the
% debugger.
this.callExternalDebugger('unload application');
errFlag = false;
end
% Closes the debugger. Optional.
function errFlag = close(this)
% Add logic here to close the debugger. If you implemented the
% 'open' method, you should implement this method.
this.callExternalDebugger('close');
errFlag = false;
end
end
methods (Access=private)
function output = callExternalDebugger(~, varargin)
output = [];
fprintf("\nInstructed the debugger to '%s'\n", varargin{1});
if nargin > 2
disp('Using arguments:');
disp(varargin(2:end));
end
end
end
end
Use Debugger for PIL Target Connectivity
To provide PIL connectivity between Simulink and the target hardware, you can use the target namespace with a debugger, for example, the GNU® Debugger.
Implement the target.DebugIOTool debugger abstraction interface
Implement the interface in a class calledmyImplementationClass
. For an example pseudocode implementation, see DebugIOTool Template.
classdef myImplementationClass < target.DebugIOTool
properties (Access=private)
…
endmethods
…
end
methods (Access=private)
…
end
end
2. Create the board description
Create target.Board
object that provides MATLAB with a description of your target hardware.
hostTarget = target.create('Board', ...
'Name', 'GDB Debug PIL IO');
3. Associate the board with a processor
To support code generation, associate the board with atarget.Processor
object that contains a language implementation. For this example, create atarget.Processor
object and reuse an existingtarget.LanguageImplementation
object. For information about setting up a customtarget.LanguageImplementation
object, see Register New Hardware Devices.
hostTarget.Processors = target.get('Processor', ...
'Intel-x86-64 (Linux 64)');
4. Describe the debugger execution service implementation
Create a target.ExecutionService
object that references the debugger abstraction interface.
matlabExecution = target.create('ExecutionService', ...
'Name', 'GDB Launch');
matlabExecution.APIImplementation = ...
target.create('APIImplementation', ...
'Name', 'myServiceImplementation');
matlabExecution.APIImplementation.BuildDependencies = ...
target.create('MATLABDependencies');
matlabExecution.APIImplementation.BuildDependencies.Classes = ...
{'myImplementationClass'};
matlabExecution.APIImplementation.API = ...
target.get('API', ...
'DebugIOTool');
5. Associate debugger execution service with the board
Associate the target.ExecutionService
object with the target.Board
object.
hostTarget.Tools.DebugTools = matlabExecution;
6. Make board object persist across MATLAB sessions
Add the board object to an internal database and make the object persist across MATLAB sessions.
target.add(hostTarget, 'UserInstall', true);
7. Specify board for model
Before you run a PIL simulation, in the Configuration Parameters dialog box, set Hardware board to GDB Debug PIL IO
.
DebugIOTool
Template
This section provides a pseudocode example oftarget.DebugIOTool
debugger abstraction. Use atarget.DebugIOTool
object to:
- Control the lifetime of an application running in the debugger.
- Automate common debugger actions. For example, applying breakpoints and continuing execution from a paused state.
- Read and write to memory that the application uses.
For interactions with the debugger, you can use MATLAB external language interfaces to APIs, which the debugger vendor supplies.
Section Descriptions for target.DebugIOTool Debugger Abstraction Example
target.DebugIOTool Object | Comments |
---|---|
classdef DebugIOToolTemplate < target.DebugIOTool | Example implementation oftarget.DebugIOTool debugger abstraction. |
properties (Access=private) DebuggerHandle; end | Optional. Add any state required to interact with the debugger or application being run in the debugger as a property of DebugIOTool. Apply private access so that only this file can access the state. For example, the state can be a handle to the debugger application provided by the debugger external API. |
methods | Method implementations that perform debugger interactions. Each method returns a'errFlag' logical flag. If the method executes, 'errFlag' is set tofalse. Consider also using the MATLAB error method to report custom error information. |
function this = DebugIOToolTemplate() end | Optional.Constructor for thetarget.DebugIOTool. The constructor creates an object of the tool allowing its methods to be called from that object. Perform basic initialization here. For example, setting state for calls toopen. |
function errFlag = open(this) this.DebuggerHandle = this.callExternalDebugger('open'); errFlag = false; end | Optional.Add logic here to open the debugger. Typically, use system commands or the external API provided by the debugger.If you cannot open the debugger without also loading the application into the debugger, do not implement this method. Use the'loadApplication' method to implement the logic.If you cannot open the debugger without also starting execution of the application, do not implement this method. Use the'startApplication' method to implement the logic. |
function errFlag = loadApplication(this) this.callExternalDebugger('load application', ... this.Application, .... this.ApplicationCommandLineArguments); this.callExternalDebugger('set breakpoints', this.Breakpoints); errFlag = false; end | Optional.Load the application into the debugger.Add logic here to load the application into the debugger. For example, load a project into an IDE debugger or load the application executable file into the debugger harness.Also add logic to set breakpoints in the debugger to break execution.The path to the application file is stored in the Application property of this class which can you can access by usingthis.Application.Additional command line arguments for the application are stored inthis.ApplicationCommandLineArguments.Breakpoints to set are stored inthis.Breakpoints.If you cannot load the application without also starting execution of the application, do not implement this method. Implement the logic in the 'startApplication' method. |
function errFlag = startApplication(this) this.callExternalDebugger('start application'); errFlag = false; end | Required.Start the application execution in the debugger.Add logic here to start the application execution in the debugger.The path to the application to load is stored in theApplication property of this class, which you can access by usingthis.Application.Additional command line arguments to pass to the application are stored inthis.ApplicationCommandLineArguments.Breakpoints to set are stored inthis.Breakpoints. |
function errFlag = stopApplication(this) this.callExternalDebugger('stop application'); errFlag = false; end | Optional.Stop the application execution in the debugger.Add logic here to stop the application execution in the debugger.If your'startApplication' method opened the debugger, perform the opposite action here, i.e. close the debugger.If your'startApplication' method loaded the application, perform the opposite action here, i.e. unload the application from the debugger. |
function errFlag = unloadApplication(this) this.callExternalDebugger('unload application'); errFlag = false; end | Optional.Unloads the application from the debugger.If you implemented the'loadApplication' method, then add logic here to unload the application from the debugger.If your'loadApplication' method opened the debugger, perform the opposite action here, i.e. close the debugger. |
function errFlag = close(this) this.callExternalDebugger('close'); errFlag = false; end | Optional.Closes the debugger.Add logic here to close the debugger. If you implemented the 'open' method, you must also implement the 'close' method. |
function [atBreakpoint, errFlag] = ... stoppedAtBreakpoint(this, breakpoint) result = this.callExternalDebugger('determine breakpoint hit', ... breakpoint.File, ... breakpoint.Line, ... breakpoint.Function); atBreakpoint = ~isempty(result); errFlag = false; end | Required.Asks if the debugger is currently stopped at the specified breakpoint. Returnstrue if at the specified breakpoint,false otherwise.Add logic to determine if the debugger has broken execution at the breakpoint passed into the function.The'breakpoint' input variable is of type target.Breakpoint, which provides the description of the breakpoint. It contains propertiesFunction, File, and Line which describes the location of the breakpoint.Set theatBreakpoint return value totrue if the debugger has stopped at the breakpoint, or false otherwise. |
function errFlag = continueExecution(this) this.callExternalDebugger('continue execution'); errFlag = false; end | Required.Continues execution if application is paused in the debugger. |
function errFlag = write(this, variableName, memoryStream) sizeOfMemoryVariable = uint64(numel(memoryStream)); arrayFormat = repmat('%d,', [1 sizeOfMemoryVariable]); arrayFormat(end) = []; this.callExternalDebugger(sprintf(['execute (void*)memcpy(%s,' ... '(unsigned char [%d]){%s}, %d)'], ... variableName, ... sizeOfMemoryVariable, ... sprintf(arrayFormat, memoryStream), ... sizeOfMemoryVariable)); errFlag = false; end | Write the memory stream to target platform memory specified by the variable name. Add logic to write memoryStream data to the variablevariableName. The function assumes that code execution has reached the breakpoint that requires this action.memoryStream is a vector of uint64 values. Theunit64 values act as a container for the smallest addressable integer on the target platform, which in most cases is the size of char, typically 8 bits.The variable represents a pointer to the smallest addressable integer type. For example, write operations on a target platform wherechar represents the smallest addressable integer are equivalent to thememcpy call in C code:(void*)memcpy(, (unsigned char[]) {<memoryStream(1)>,<memoryStream(2)>}, );If your debugger allows immediate execution of code when execution is broken, use the memcpy approach to write data to the variable. |
function [memoryStream, errFlag] = read(this, ... variableName, ... sizeToRead) memoryStream = this.callExternalDebugger(... sprintf("read (unsigned char [%d])*%s", ... sizeToRead, variableName)); memoryStream = uint64(memoryStream); errFlag = false; end | Read the memory stream from target platform memory specified by variable name.Add logic to read from the variableName variable on the target platform. The function assumes that code execution has reached the breakpoint that requires this action.The memoryStream return value is a vector of uint64 values. The unit64 values act as a container for the smallest addressable integer on the target platform, which in most cases is the size ofchar, typically 8 bits.The variable represents a pointer to the smallest addressable integer type. Read operations are equivalent to this C code:(unsigned char [])*If your debugger allows immediate execution of code when execution is broken, use the C code approach to inspect the value of the buffer and return it to MATLAB. |
end | end statement formethods. |
methods (Access=private) function output = callExternalDebugger(~, varargin) output = []; fprintf("\nInstructed the debugger to '%s'\n", varargin{1}); if nargin > 2 disp('Using arguments:'); disp(varargin(2:end)); end end end | Private methods. |
end | end statement forclassdef. |
Pseudocode for target.DebugIOTool Debugger Abstraction Example
classdef DebugIOToolTemplate < target.DebugIOTool % DEBUGIOTOOLTEMPLATE Example implementation of target.DebugIOTool % debugger abstraction.
% Copyright 2020 The MathWorks, Inc.
properties (Access=private)
% Optional
% Add any state required to interact with the debugger or
% application being run in the debugger as a property of
% DebugIOTool. Apply private access so that only this file
% can access the state.
%
% For example, the state could be a handle to the debugger
% application provided by the debugger external API.
DebuggerHandle;
end
% Method implementations that perform debugger interactions.
% Each method will return a 'errFlag' logical flag. If the method
% executes, 'errFlag' is set to false.
% Consider also using the MATLAB error method to report custom
% error information.
methods
% Constructor for the target.DebugIOTool. The constructor
% creates an instance of the tool allowing its methods to be called
% on that instance. Optional.
function this = DebugIOToolTemplate()
% Basic initialization can be performed here. For example
% setting state ready for calls to open
end
% Open the debugger. Optional.
function errFlag = open(this)
% Add logic here to open the debugger. Typically, use system
% commands or the external API provided by the debugger.
%
% If you cannot open the debugger without also loading the
% application into the debugger, this method should not be
% implemented and the logic should be performed in the
% 'loadApplication' method.
%
% If you cannot open the debugger without also starting
% execution of the application, this method should not be
% implemented and the logic should be performed in the
% 'startApplication' method.
this.DebuggerHandle = this.callExternalDebugger('open');
errFlag = false;
end
% Load the application into the debugger. Optional.
function errFlag = loadApplication(this)
% Add logic here to load the application into the debugger.
% This could be loading a project into an IDE debugger or
% loading the application executable into the debugger harness.
%
% Additionally add logic to set breakpoints in the debugger to
% break execution.
%
% The path to the application to load is stored in the
% Application property of this class which can be accessed
% using this.Application in this method.
%
% Additional command line arguments to pass to the
% application are stored in
% this.ApplicationCommandLineArguments.
%
% Breakpoints to set are stored in this.Breakpoints
%
% If you cannot load the application without also starting
% execution of the application, this method should not be
% implemented and the logic should be performed in the
% 'startApplication' method.
this.callExternalDebugger('load application', ...
this.Application, ....
this.ApplicationCommandLineArguments);
this.callExternalDebugger('set breakpoints', this.Breakpoints);
errFlag = false;
end
% Start the application execution in the debugger. Required.
function errFlag = startApplication(this)
% Add logic here to start the application execution in the
% debugger.
%
% The path to the application to load is stored in the
% Application property of this class which can be accessed
% using this.Application in this method.
%
% Additional command line arguments to pass to the
% application are stored in
% this.ApplicationCommandLineArguments.
%
% Breakpoints to set are stored in this.Breakpoints
this.callExternalDebugger('start application');
errFlag = false;
end
% Stop the application execution in the debugger. Optional.
function errFlag = stopApplication(this)
% Add logic here to stop the application execution in the
% debugger.
% If your 'startApplication' method opened the debugger,
% perform the opposite action here, i.e. close the
% debugger.
%
% If your 'startApplication' method loaded the application,
% perform the opposite action here, i.e. unload the
% application from the debugger.
this.callExternalDebugger('stop application');
errFlag = false;
end
% Unloads the application from the debugger. Optional.
function errFlag = unloadApplication(this)
% If you implemented the 'loadApplication' method,
% then add logic here to unload the application from
% the debugger.
% If your 'loadApplication' method opened the debugger,
% perform the opposite action here, i.e. close the
% debugger.
this.callExternalDebugger('unload application');
errFlag = false;
end
% Closes the debugger. Optional.
function errFlag = close(this)
% Add logic here to close the debugger. If you implemented the
% 'open' method you should implement this method.
this.callExternalDebugger('close');
errFlag = false;
end
% Asks if the debugger is currently stopped at the specified
% breakpoint. Returns true if at the specified breakpoint, false
% otherwise. Required.
function [atBreakpoint, errFlag] = ...
stoppedAtBreakpoint(this, breakpoint)
% Add logic to determine if the debugger has broken execution
% at the breakpoint passed into the function.
%
% The 'breakpoint' input variable is of type target.Breakpoint
% which provides the description of the breakpoint. It contains
% properties Function, File and Line which describes the
% location of the breakpoint.
%
% Set the atBreakpoint return value to true if the debugger is
% stopped at the breakpoint, or false if not.
result = this.callExternalDebugger('determine breakpoint hit', ...
breakpoint.File, ...
breakpoint.Line, ...
breakpoint.Function);
atBreakpoint = ~isempty(result);
errFlag = false;
end
% Continues execution if application execution is paused in the
% debugger. Required.
function errFlag = continueExecution(this)
this.callExternalDebugger('continue execution');
errFlag = false;
end
% Write the specified memory stream to the target platform memory
% specified by the given variable name.
function errFlag = write(this, variableName, memoryStream)
% Add logic to write the memoryStream data to the variableName
% variable in the target. This function should assume that the
% appropriate breakpoint has been hit to perform such an
% action.
%
% memoryStream is a vector of uint64 values to be written to
% the target platform. The unit64 values act as a container for
% the smallest addressable integer on the target platform. In
% most cases this will be a container for the size of char on
% the target platform, which will commonly be 8-bit.
%
% The specified variable will represent a pointer to the
% smallest addressable integer type.
% As an example write operations on a target platform where
% char represents the smallest addressable integer should be
% equivalent to the following memcpy call in C code:
%
% (void*)memcpy(<variableName>,
% (unsigned char[<sizeOfMemoryStream>])
% {<memoryStream(1)>,<memoryStream(2)>, ...},
% <sizeOfMemoryStream>);
%
% A lot of debuggers allow for immediate execution of code when
% execution is broken, if so, attempt to use the above command
% to write data to the variable like the below example:
sizeOfMemoryVariable = uint64(numel(memoryStream));
arrayFormat = repmat('%d,', [1 sizeOfMemoryVariable]);
arrayFormat(end) = [];
this.callExternalDebugger(sprintf(['execute (void*)memcpy(%s,' ...
'(unsigned char [%d]){%s}, %d)'], ...
variableName, ...
sizeOfMemoryVariable, ...
sprintf(arrayFormat, memoryStream), ...
sizeOfMemoryVariable));
errFlag = false;
end
% read the memory stream from the target platform memory specified
% by the given variable name.
function [memoryStream, errFlag] = read(this, ...
variableName, ...
sizeToRead)
% Add logic to read from the variableName variable on the
% target. This function should assume that the appropriate
% breakpoint has been hit to perform such an action.
%
% The memoryStream return value should be a vector of uint64
% values. The unit64 values act as a container for the smallest
% addressable integer on the target platform. In most cases
% this will be a container for the size of char on the target
% platform, which will commonly be 8-bit.
%
% The specified variable will represent a pointer to the
% smallest addressable integer type.
% Read operations should be equivalent to inspecting the result
% of the following C code:
% (unsigned char [<sizeToRead>])*<variableName>
%
% A lot of debuggers allow for immediate execution of code when
% execution is broken, if so, attempt to use the above command
% to inspect the value of the buffer and return it to MATLAB.
memoryStream = this.callExternalDebugger(...
sprintf("read (unsigned char [%d])*%s", ...
sizeToRead, variableName));
% The memory stream must be returned as a vector of uint64
% values
memoryStream = uint64(memoryStream);
errFlag = false;
end
end
methods (Access=private)
function output = callExternalDebugger(~, varargin)
output = [];
fprintf("\nInstructed the debugger to '%s'\n", varargin{1});
if nargin > 2
disp('Using arguments:');
disp(varargin(2:end));
end
end
end
end