Showing posts with label adr. Show all posts
Showing posts with label adr. Show all posts

November 17, 2020

Переменная через указатель, через ADS

Значение переменной можно прочитать через указатель по ADS.

PROGRAM MAIN
VAR
    state:  UINT;
    pState: POINTER TO UINT;
END_VAR

pState := ADR(state);


Прочитаем значение переменной `state` через ее указатель `pState`. Нужно разыменовать указатель, добавив спец. символ `^` к имени указателя. Получим `pState^`. Программа на C# для текущей версии 4.4.10 библиотеки Beckhoff.TwinCAT.Ads:

ushort state = (ushort) tcAdsClient.ReadSymbol( "MAIN.pState^", typeof( ushort ), false );


Значение переменной читается. Теперь возьмем пробную версию новой библиотеки 5.0.1-preview.12 под .NET Core 3.1.0. Код поподробнее:


Программа читает переменную и в зависимости от ее значения рисует замысловатые узоры. Имея указатель можно вытягивать значение переменной. Это полезное свойство и дополнительные телодвижения здесь не нужны.


Для новой библиотеки под .NET Core требуется создать файл роутинга `StaticRoutes.xml`. В этом файле настраиваются соединения между целевым контроллером и ПК. Если контроллер  и ПК находятся на одной и той же машине, то настраивать этот файл не нужно. Можно просто удалить его.

February 9, 2017

Указатель на ФБ

Указатели — это прямой путь в память контроллера. Путь — ничем не ограниченный и крайне опасный: одно не верное движение и вот вы уже в неразмеченной области памяти, отстреливаете себе ногу, вызывая исключение Page Fault! PLC is stopped. Что делать и как быть?

Начнем с простого:

PROGRAM MAIN
VAR
    A  : INT;
    B  : WORD;
    
    pA : POINTER TO INT;
    pB : POINTER TO WORD;
    dw : DWORD;
END_VAR

[...]

pA  := ADR(A);
pA^ := -1;


Переменная А в результате будет равна -1. При попытке сделать аналогичное с переменной B — pB^ := -1; получим ошибку компилятора еще на этапе сборки проекта. Объяснение простое — мы объявили pB как указатель на целый и всегда положительный тип WORD, а пытаемся пропихнуть число со знаком, типа INT. Компилятор бдит.


Выход за границу


Вообще, указателям можно присваивать все, что угодно, лишь бы справа был POINTER TO или DWORD (UDINT):

dw  := ADR(B);
pB  := dw;
pB^ := 123;

ADR возвращает число типа DWORD — это адрес переменной. Поэтому можно присвоить этот адрес указателю pB, а затем разыменовать указатель с помощью оператора ^ и присвоить новое значение для указанной переменной B. После всех действий B будет равен 123.

Оператор ^ применим только к указателям. Нельзя разыменовать переменную другого типа: dw^ := 123; — получим ошибку: '^' needs a pointer type. Поэтому хранить адрес можно в обычных переменных, но работать с адресами получится только через указатели.

Добавим перца — указателю можно присвоить любой адрес или любой другой указатель, указывающий на произвольный тип:

pA  := pB; (* pA теперь указывает на переменную 'B' типа WORD *)
pA^ := -2;

Результат: B = 65534 и ошибки здесь нет. Мы объявляли pA, как указатель на целое со знаком, то есть переменную типа INT, а переменная B — это целое беззнаковое, поэтому бит знака превратился в значимый разряд и дальше бла-бла-бла...

До сих пор у нас совпадал размер переменных — обе занимали ровно два байта. Что будет, если мы сделаем так:

A   : INT;
B   : BYTE;

[...]

pA  := pB; (* pA теперь указывает на переменную 'B' типа BYTE *)
pA^ := 1234;

B = 210;

...и опять без ошибки, но она может легко возникнуть, так как мы уже вышли за пределы переменной: 1234 занимает в памяти два байта, а мы записываем это число в переменную B типа BYTE, длиной один байт, как нам сообщает Капитан Очевидность. Таким образом легко совершить целый ряд безобразий: вылезти за пределы переменной или массива, вызвать сбой при обращении к странице памяти, остановить контроллер и технологический процесс. Завод встал, рабочие с факелами идут карать Франкенштейна.


Указатель на код


Точнее, на функциональные блоки, ведь на программы ссылаться нельзя.

Для этого эксперимента нам понадобится:
  • Глобальная переменная, значение которой будет изменяться функциональными блоками.
  • Два ФБ, по разному изменяющие содержимое глобальной переменной.
  • Два указателя, которые мы будем испытывать, сталкивая лбами.
  • Немного терпения разработчика.

А давайте как физики в эксперименте про квантовую телепортацию — назовем наши функциональные блоки "Алиса" и "Боб" (в алфавитном порядке):

VAR_GLOBAL
    g_Result : STRING := 'Хзкт';
END_VAR

[...]

FUNCTION_BLOCK Alice
VAR_INPUT
    Name : STRING; (* пригодится позднее, когда появится злой двойник *)
END_VAR

g_Result := 'Алиса';

[...]

FUNCTION_BLOCK Bob
g_Result := 'Боб';

[...]

PROGRAM MAIN
VAR
    Al  : Alice;
    Bo  : Bob;
    pAl : POINTER TO Alice;
    pBo : POINTER TO Bob;
END_VAR

[...]

pAl := ADR(Al);    (* Указатель на ФБ Алиса *)
pBo := ADR(Bo);    (* Указатель на ФБ Боб *)

pAl^(); (* ФБ можно вызывать через разыменованный указатель *)


Каждый из ФБ записывает в глобальную переменную g_Result соответствующее имя: в зависимости от того, чей экземпляр мы вызовем через указатель, мы получим разные имена в глобальной переменной. На этот раз, получим результат 'Алиса'.

Попробуем подменить адрес указателя на другой, не обращая внимание, что пытаемся предложить неправильный тип данных:

pAl := ADR(Bo);  (* Указатель на ФБ Боб *)
pAl^();


И если вы подумали, что получился 'Боб', то вы не правы — g_Result по прежнему равно 'Алиса'. Система следит за типом указателя, не давая сменить его на неправильный, но делает это молча — никому, ни о чем не сообщая.

Давайте создадим второй экземпляр ФБ Alice и переведем указатель на него, то есть присвоим указателю указатель, но теперь правильного типа POINTER TO Alice:

VAR
    Al     : Alice := (Name := 'Alice Original');
    AlTwin : Alice := (Name := 'Alice Evil Twin');

[...]

pAl := ADR(Al);       (* Указатель на ФБ Алиса *)
pAl := ADR(AlTwin);   (* Указатель на ФБ близнеца Алисы *)
pAl^();


На этот раз, указатель изменился:



TwinCAT 3


Скопируем проект, с небольшими отличиями в объявлении глобальной переменной:

VAR_GLOBAL
    Result : STRING := 'Хзкт';
END_VAR

[...]

FUNCTION_BLOCK Alice
G.Result := 'Алиса';  (* аналогично для Боба *)

[...]
    
pAl := ADR(Al);       (* Указатель на ФБ Алиса *)
pBo := ADR(Bo);       (* Указатель на ФБ Боб *)

pAl := pBo;

pAl^();


В результате получим — 'Боб', то есть G.Result = 'Боб'. В TwinCAT 3 указатели... э-э-э, гибкие? изменчивые? динамические? отзывчивые?


Нечеловеческий эксперимент над роботами


Жуткий по сложности и непонятности эксперимент над указателями в TwinCAT 2:
  1. Ссылаемся указателем pA на функциональный блок Alice.
  2. Получаем адрес функционального блока Bo.
  3. Получаем группу:смещение указателя pA.
  4. Через функции ADS записываем (подменяем) адрес указателя pA.
  5. Разыменовываем указатель pA и вызываем функциональный блок.

Зачем это нужно? Если раньше мы могли предположить, что компилятор как-то там отслеживает все наши махинации при сборке проекта: контролирует и корректирует указатели, то теперь мы те же действия будем делать уже во время работы контроллера. Компилятор о них ничего не узнает:

PROGRAM MAIN
VAR
    ReadSymInfo : PLC_ReadSymInfoByName;
    SymInfo     : SYMINFOSTRUCT;
    WriteAds    : ADSWRITE;

    Al          : Alice;
    pAl         : POINTER TO Alice;
    Bo          : Bob;
    AddrBob     : UDINT;

    state       : UINT;
END_VAR

[...]

CASE state OF
0:
    pAl     := ADR(Al); (* 1 *)
    AddrBob := ADR(Bo); (* 2 *)
    state   := 100;

100: (* 3 *)
    ReadSymInfo(
        NETID   := '',
        PORT    := 801,
        SYMNAME := 'MAIN.pAl',
        START   := TRUE,
        SYMINFO => SymInfo);

    IF NOT ReadSymInfoByName.BUSY THEN
        ReadSymInfoByName(START := FALSE);
        state := 200;
    END_IF

200: (* 4 *)
    WriteAds(
        NETID   := '',
        PORT    := 801,
        IDXGRP  := SymInfo.idxGroup,
        IDXOFFS := SymInfo.idxOffset,
        LEN     := 4,
        SRCADDR := ADR(AddrBob),
        WRITE   := TRUE);

    IF NOT WriteAds.BUSY THEN
        WriteAds(WRITE := FALSE);
        state := 300;
    END_IF

300:
    pAl^(); (* 5 *)

END_CASE

В результате все равно получится 'Алиса' и это несмотря на то, что указатель, судя по адресу, указывает на 'Боба':



Предположения


В TwinCAT 2 указатели константные, плюс махинации с таблицей символьной информации (SymbolInfo), которая как-то неявно привязана к переменным. Код компилируется раз и навсегда, а вот данные переменных транслируются через специальную таблицу символов и могут изменяться. Указатели стоят где-то на стыке между кодом и переменными, поэтому адрес указателя мы изменить можем, но вызов кода уже не изменим, так как он уже скомпилирован. В общем, указатели в TwinCAT 2 только для данных и ограниченно для кода.

В TwinCAT 3 завезли объектно-ориентированное программирование с классами, методами и, самое главное, интерфейсами. Последние как раз нуждаются в полиморфизме и гибких указателях (vtable). Поэтому с указателями в TwinCAT 3 все ожидаемо-хорошо.

И там, и там можно устроить крах системы, выйдя за пределы выделенной памяти. Осторожнее там с указателями!

July 5, 2016

Ссылки и указатели

Переменные, как и ложка, не существуют. Это абстракции, освобождающие программиста от труда по управлению памятью. Нечто, называемое переменной, на самом деле всего-лишь название для ячейки памяти, в которой хранятся данные. Несмотря на это, нам по прежнему необходимо нечто противоположное — возможность поработать с памятью напрямую. В языках высокого уровня для этого существуют ссылки и указатели.
Ссылка и указатель — это имя ячейки памяти в которой хранится адрес другой ячейки памяти в которой хранятся данные. По другому это называется косвенная адресация и можно пойти дальше: указатель на указатель на...


Указатели


POINTER TO — это указатель на переменную. Может указывать как на данные, так и на другие указатели, а также на функциональные блоки, программы, методы и функции. Не могут указывать на ссылки.
Минутка занудства: можно сделать так — POINTER TO POINTER TO POINTER TO..., но особого смысла это не имеет, а вот указатель-на-указатель иногда применяется. Например, когда необходимо переключаться между несколькими переменными или функциями.

К указателям применима арифметика: к адресу указателя можно прибавить число и наш указатель будет указывать на другую ячейку памяти, т. е. на совсем другие данные. Для этого указатели и нужны. В этом месте обычно напоминают про "отстрелить себе ногу", т. к. очень легко наприбавлять столько, что указатель будет указывать в неизвестном направлении.

Массивы — это тоже указатели, только завуалированные; и это была первая попытка человечества перестать отстреливать себе конечности, так как для массивов есть проверка на выход за границы массива.
Указатель на массив повторно разрешает членовредительство.

Чтобы подобраться ближе к данным, на которые указывает указатель, используют операцию ^  -разыменование указателя. Указатель от этого не портится и не разыменовывается, мы просто записываем данные в ту ячейку, на которую указывает указатель.

PROGRAM MAIN
VAR
    pt_a    : POINTER TO INT;
    pt_pt_a : POINTER TO POINTER TO INT;
    fbTest  : FB_Test;
    pt_fb   : POINTER TO FB_Test;

// [...]

a       := 123;
pt_a    := ADR(a);             // pt_a указывает на 'a'
pt_pt_a := ADR(pt_a);          // pt_pt_a указывает на pt_a который указывает на 'a'
pt_a^   := 67;                 // 'a' теперь равно 67
pt_fb   := ADR(fbTest);

fbTest(inp := a, in_out := a);
pt_fb^(inp := a, in_out := a); // разыменование указателя на ФБ и запуск ФБ


Ссылки


REFERENCE TO — ссылаться можно только на данные (и структуры, они тоже данные) и указатели (получим второе имя для указателя). Ссылки с арифметикой не дружат, поэтому они надежнее: выйти за границы приличия не получится.

Можно считать, что ссылка это та же самая переменная только с другим именем: ячейка памяти одна — имени два (или три, или четыре, сколько пожелаете).

PROGRAM MAIN
VAR
    a         : INT;
    ref_a     : REFERENCE TO INT;
    ref_pt_a  : REFERENCE TO POINTER TO INT;
    pt_ref_a  : POINTER TO INT;

// [...]

a         :=    123;
ref_a     REF=  a;
pt_ref_a  :=    ADR(ref_a); // указатель на ссылку
ref_a     :=    67;         // 'a' теперь равно 67
ref_a     REF=  0;          // ссылка больше ни на что не указывает
pt_ref_a^ :=    44;         // 'a' теперь равно 44


Для получения ссылки используется оператор REF=. Слитно три буквы и знак равно, все без пробелов. Это и есть оператор получения ссылки. На картинке ниже видно как переменная и ссылка на нее принимают одинаковое значение = 67:



Стоит обратить внимание на указатель pt_ref_a, который хоть и пытается указывать на ссылку ref_a, но мы то уже знаем, что ref_a это всего-лишь второе название для переменной 'a'. Поэтому в итоге указатель указывает на переменную 'a' типа INT.



После присваивания ссылке нуля (в предпоследней строке), указатель pt_ref_a по прежнему указывает на переменную 'a', хотя ссылка ref_a уже никуда не ссылается. Это подтверждает что мы просто удаляем "второе" название переменной 'а', в то время как сама переменная остается на месте (соответственно, сохраняется и указатель на нее).


Зачем ссылки?


Предположим, мы хотим передать в ФБ переменную, которую ФБ обработает, а затем запишет в эту переменную новое значение (так часто поступают со структурами, когда хотят заполнить их интересными данными или нулями). Проще всего это можно сделать передав указатель на переменную, но тогда возникает опасность, что внутри себя ФБ воспользуется арифметикой указателей и сдвинет указатель настолько, что в итоге обратится к запрещенному и обрушит всю систему. Что делать?

Давайте воспользуемся ссылкой, ведь для нее арифметика не работает! Все правильно, и за нас это уже сделали.

Любой ФБ, кроме входных параметров VAR_INPUT и VAR_OUTPUT имеет набор VAR_IN_OUT. Если первые два принимают входные данные по значению, то последний IN_OUT принимает входной параметр как ссылку. Изменяя ее значение внутри ФБ, мы сможем изменять значение переменной снаружи ФБ, ведь ссылка это второе имя переменной.

FUNCTION_BLOCK FB_Test
VAR_INPUT
    inp    : INT;

VAR_OUTPUT
    out    : INT;

VAR_IN_OUT
    in_out : INT;

// [...]

inp    := 44;
out    := 55;
in_out    := 77;


Запускаем и смотрим в отладчике на результат:



ФБ изнутри изменил значение внешней переменной 'a', сменив ее значение на = 77. Изменение входного параметра inp внутри ФБ на = 44, никак не повлияло на значение переменной 'a', так как он принимает параметр по значению (просто создает локальную копию значения).


Массивы переменной длины


Мы разрабатываем ФБ, который как-то там должен изменять содержимое массива (например, заполнять латинскими лорем-ипсумами или нулями). Внимание, вопрос — как передавать в ФБ массивы разной длины?

FUNCTION_BLOCK POUPIUPOW
VAR_IN_OUT
    VarArray : ARRAY [*] OF INT;
END_VAR
VAR
    start    : DINT;
    end      : DINT;
END_VAR

// [...]

start := LOWER_BOUND(VarArray, 1);
end   := UPPER_BOUND(VarArray, 1);


Работает только для VAR_IN_OUT параметров (иначе как бы мы влияли на внешние к ФБ массивы).
Совместимо с третьей редакцией стандарта МЭК.
Для определения габаритов или просто начала и конца массива используются функции LOWER_BOUND и UPPER_BOUND. Второй параметр этих функций — это номер размерности: 1 — получить длину одномерного массива; 1..2 — длину строки или кол-во столбцов для двумерного массива; 1..3 — длина строки, столбца или глубины для 3D массива.